MediaWiki 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, $parser ) .
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 $isBadFile = $file && $thumb &&
417 $parser->getBadFileLookup()->isBadFile( $title->getDBkey(), $parser->getTitle() );
418
419 if ( !$thumb || $isBadFile ) {
420 $s = self::makeBrokenImageLinkObj( $title, $frameParams['title'], '', '', '', $time == true );
421 } else {
422 self::processResponsiveImages( $file, $thumb, $handlerParams );
423 $params = [
424 'alt' => $frameParams['alt'],
425 'title' => $frameParams['title'],
426 'valign' => $frameParams['valign'] ?? false,
427 'img-class' => $frameParams['class'] ];
428 if ( isset( $frameParams['border'] ) ) {
429 $params['img-class'] .= ( $params['img-class'] !== '' ? ' ' : '' ) . 'thumbborder';
430 }
431 $params = self::getImageLinkMTOParams( $frameParams, $query, $parser ) + $params;
432
433 $s = $thumb->toHtml( $params );
434 }
435 if ( $frameParams['align'] != '' ) {
436 $s = Html::rawElement(
437 'div',
438 [ 'class' => 'float' . $frameParams['align'] ],
439 $s
440 );
441 }
442 return str_replace( "\n", ' ', $prefix . $s . $postfix );
443 }
444
453 private static function getImageLinkMTOParams( $frameParams, $query = '', $parser = null ) {
454 $mtoParams = [];
455 if ( isset( $frameParams['link-url'] ) && $frameParams['link-url'] !== '' ) {
456 $mtoParams['custom-url-link'] = $frameParams['link-url'];
457 if ( isset( $frameParams['link-target'] ) ) {
458 $mtoParams['custom-target-link'] = $frameParams['link-target'];
459 }
460 if ( $parser ) {
461 $extLinkAttrs = $parser->getExternalLinkAttribs( $frameParams['link-url'] );
462 foreach ( $extLinkAttrs as $name => $val ) {
463 // Currently could include 'rel' and 'target'
464 $mtoParams['parser-extlink-' . $name] = $val;
465 }
466 }
467 } elseif ( isset( $frameParams['link-title'] ) && $frameParams['link-title'] !== '' ) {
468 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
469 $mtoParams['custom-title-link'] = Title::newFromLinkTarget(
470 $linkRenderer->normalizeTarget( $frameParams['link-title'] )
471 );
472 } elseif ( !empty( $frameParams['no-link'] ) ) {
473 // No link
474 } else {
475 $mtoParams['desc-link'] = true;
476 $mtoParams['desc-query'] = $query;
477 }
478 return $mtoParams;
479 }
480
493 public static function makeThumbLinkObj( LinkTarget $title, $file, $label = '', $alt = '',
494 $align = 'right', $params = [], $framed = false, $manualthumb = ""
495 ) {
496 $frameParams = [
497 'alt' => $alt,
498 'caption' => $label,
499 'align' => $align
500 ];
501 if ( $framed ) {
502 $frameParams['framed'] = true;
503 }
504 if ( $manualthumb ) {
505 $frameParams['manualthumb'] = $manualthumb;
506 }
507 return self::makeThumbLink2( $title, $file, $frameParams, $params );
508 }
509
520 public static function makeThumbLink2( LinkTarget $title, $file, $frameParams = [],
521 $handlerParams = [], $time = false, $query = "", ?Parser $parser = null
522 ) {
523 $exists = $file && $file->exists();
524
525 $page = $handlerParams['page'] ?? false;
526 if ( !isset( $frameParams['align'] ) ) {
527 $frameParams['align'] = 'right';
528 }
529 if ( !isset( $frameParams['alt'] ) ) {
530 $frameParams['alt'] = '';
531 }
532 if ( !isset( $frameParams['title'] ) ) {
533 $frameParams['title'] = '';
534 }
535 if ( !isset( $frameParams['caption'] ) ) {
536 $frameParams['caption'] = '';
537 }
538
539 if ( empty( $handlerParams['width'] ) ) {
540 // Reduce width for upright images when parameter 'upright' is used
541 $handlerParams['width'] = isset( $frameParams['upright'] ) ? 130 : 180;
542 }
543 $thumb = false;
544 $noscale = false;
545 $manualthumb = false;
546
547 if ( !$exists ) {
548 $outerWidth = $handlerParams['width'] + 2;
549 } else {
550 if ( isset( $frameParams['manualthumb'] ) ) {
551 # Use manually specified thumbnail
552 $manual_title = Title::makeTitleSafe( NS_FILE, $frameParams['manualthumb'] );
553 if ( $manual_title ) {
554 $manual_img = MediaWikiServices::getInstance()->getRepoGroup()
555 ->findFile( $manual_title );
556 if ( $manual_img ) {
557 $thumb = $manual_img->getUnscaledThumb( $handlerParams );
558 $manualthumb = true;
559 } else {
560 $exists = false;
561 }
562 }
563 } elseif ( isset( $frameParams['framed'] ) ) {
564 // Use image dimensions, don't scale
565 $thumb = $file->getUnscaledThumb( $handlerParams );
566 $noscale = true;
567 } else {
568 # Do not present an image bigger than the source, for bitmap-style images
569 # This is a hack to maintain compatibility with arbitrary pre-1.10 behavior
570 $srcWidth = $file->getWidth( $page );
571 if ( $srcWidth && !$file->mustRender() && $handlerParams['width'] > $srcWidth ) {
572 $handlerParams['width'] = $srcWidth;
573 }
574 $thumb = $file->transform( $handlerParams );
575 }
576
577 if ( $thumb ) {
578 $outerWidth = $thumb->getWidth() + 2;
579 } else {
580 $outerWidth = $handlerParams['width'] + 2;
581 }
582 }
583
584 # ThumbnailImage::toHtml() already adds page= onto the end of DjVu URLs
585 # So we don't need to pass it here in $query. However, the URL for the
586 # zoom icon still needs it, so we make a unique query for it. See T16771
587 $url = Title::newFromLinkTarget( $title )->getLocalURL( $query );
588 if ( $page ) {
589 $url = wfAppendQuery( $url, [ 'page' => $page ] );
590 }
591 if ( $manualthumb
592 && !isset( $frameParams['link-title'] )
593 && !isset( $frameParams['link-url'] )
594 && !isset( $frameParams['no-link'] ) ) {
595 $frameParams['link-url'] = $url;
596 }
597
598 $s = "<div class=\"thumb t{$frameParams['align']}\">"
599 . "<div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
600
601 $isBadFile = $exists && $thumb && $parser &&
602 $parser->getBadFileLookup()->isBadFile(
603 $manualthumb ? $manual_title : $title->getDBkey(),
604 $parser->getTitle()
605 );
606
607 if ( !$exists ) {
608 $s .= self::makeBrokenImageLinkObj( $title, $frameParams['title'], '', '', '', $time == true );
609 $zoomIcon = '';
610 } elseif ( !$thumb || $isBadFile ) {
611 $s .= wfMessage( 'thumbnail_error', '' )->escaped();
612 $zoomIcon = '';
613 } else {
614 if ( !$noscale && !$manualthumb ) {
615 self::processResponsiveImages( $file, $thumb, $handlerParams );
616 }
617 $params = [
618 'alt' => $frameParams['alt'],
619 'title' => $frameParams['title'],
620 'img-class' => ( isset( $frameParams['class'] ) && $frameParams['class'] !== ''
621 ? $frameParams['class'] . ' '
622 : '' ) . 'thumbimage'
623 ];
624 $params = self::getImageLinkMTOParams( $frameParams, $query ) + $params;
625 $s .= $thumb->toHtml( $params );
626 if ( isset( $frameParams['framed'] ) ) {
627 $zoomIcon = "";
628 } else {
629 $zoomIcon = Html::rawElement( 'div', [ 'class' => 'magnify' ],
630 Html::rawElement( 'a', [
631 'href' => $url,
632 'class' => 'internal',
633 'title' => wfMessage( 'thumbnail-more' )->text() ],
634 "" ) );
635 }
636 }
637 $s .= ' <div class="thumbcaption">' . $zoomIcon . $frameParams['caption'] . "</div></div></div>";
638 return str_replace( "\n", ' ', $s );
639 }
640
649 public static function processResponsiveImages( $file, $thumb, $hp ) {
650 global $wgResponsiveImages;
651 if ( $wgResponsiveImages && $thumb && !$thumb->isError() ) {
652 $hp15 = $hp;
653 $hp15['width'] = round( $hp['width'] * 1.5 );
654 $hp20 = $hp;
655 $hp20['width'] = $hp['width'] * 2;
656 if ( isset( $hp['height'] ) ) {
657 $hp15['height'] = round( $hp['height'] * 1.5 );
658 $hp20['height'] = $hp['height'] * 2;
659 }
660
661 $thumb15 = $file->transform( $hp15 );
662 $thumb20 = $file->transform( $hp20 );
663 if ( $thumb15 && !$thumb15->isError() && $thumb15->getUrl() !== $thumb->getUrl() ) {
664 $thumb->responsiveUrls['1.5'] = $thumb15->getUrl();
665 }
666 if ( $thumb20 && !$thumb20->isError() && $thumb20->getUrl() !== $thumb->getUrl() ) {
667 $thumb->responsiveUrls['2'] = $thumb20->getUrl();
668 }
669 }
670 }
671
684 public static function makeBrokenImageLinkObj( $title, $label = '',
685 $query = '', $unused1 = '', $unused2 = '', $time = false
686 ) {
687 if ( !$title instanceof LinkTarget ) {
688 wfWarn( __METHOD__ . ': Requires $title to be a LinkTarget object.' );
689 return "<!-- ERROR -->" . htmlspecialchars( $label );
690 }
691
692 $title = Title::castFromLinkTarget( $title );
693
695 if ( $label == '' ) {
696 $label = $title->getPrefixedText();
697 }
698 $repoGroup = MediaWikiServices::getInstance()->getRepoGroup();
699 $currentExists = $time
700 && $repoGroup->findFile( $title ) !== false;
701
703 && !$currentExists
704 ) {
705 if ( $repoGroup->getLocalRepo()->checkRedirect( $title ) ) {
706 // We already know it's a redirect, so mark it accordingly
707 return self::link(
708 $title,
709 htmlspecialchars( $label ),
710 [ 'class' => 'mw-redirect' ],
711 wfCgiToArray( $query ),
712 [ 'known', 'noclasses' ]
713 );
714 }
715
716 return Html::element( 'a', [
717 'href' => self::getUploadUrl( $title, $query ),
718 'class' => 'new',
719 'title' => $title->getPrefixedText()
720 ], $label );
721 }
722
723 return self::link(
724 $title,
725 htmlspecialchars( $label ),
726 [],
727 wfCgiToArray( $query ),
728 [ 'known', 'noclasses' ]
729 );
730 }
731
740 protected static function getUploadUrl( $destFile, $query = '' ) {
742 $q = 'wpDestFile=' . Title::castFromLinkTarget( $destFile )->getPartialURL();
743 if ( $query != '' ) {
744 $q .= '&' . $query;
745 }
746
749 }
750
753 }
754
755 $upload = SpecialPage::getTitleFor( 'Upload' );
756
757 return $upload->getLocalURL( $q );
758 }
759
769 public static function makeMediaLinkObj( $title, $html = '', $time = false ) {
770 $img = MediaWikiServices::getInstance()->getRepoGroup()->findFile(
771 $title, [ 'time' => $time ]
772 );
773 return self::makeMediaLinkFile( $title, $img, $html );
774 }
775
788 public static function makeMediaLinkFile( LinkTarget $title, $file, $html = '' ) {
789 if ( $file && $file->exists() ) {
790 $url = $file->getUrl();
791 $class = 'internal';
792 } else {
793 $url = self::getUploadUrl( $title );
794 $class = 'new';
795 }
796
797 $alt = $title->getText();
798 if ( $html == '' ) {
799 $html = $alt;
800 }
801
802 $ret = '';
803 $attribs = [
804 'href' => $url,
805 'class' => $class,
806 'title' => $alt
807 ];
808
809 if ( !Hooks::runner()->onLinkerMakeMediaLinkFile(
810 Title::castFromLinkTarget( $title ), $file, $html, $attribs, $ret )
811 ) {
812 wfDebug( "Hook LinkerMakeMediaLinkFile changed the output of link "
813 . "with url {$url} and text {$html} to {$ret}" );
814 return $ret;
815 }
816
817 return Html::rawElement( 'a', $attribs, $html );
818 }
819
830 public static function specialLink( $name, $key = '' ) {
831 if ( $key == '' ) {
832 $key = strtolower( $name );
833 }
834
835 return self::linkKnown( SpecialPage::getTitleFor( $name ), wfMessage( $key )->escaped() );
836 }
837
856 public static function makeExternalLink( $url, $text, $escape = true,
857 $linktype = '', $attribs = [], $title = null
858 ) {
859 global $wgTitle;
860 $class = "external";
861 if ( $linktype ) {
862 $class .= " $linktype";
863 }
864 if ( isset( $attribs['class'] ) && $attribs['class'] ) {
865 $class .= " {$attribs['class']}";
866 }
867 $attribs['class'] = $class;
868
869 if ( $escape ) {
870 $text = htmlspecialchars( $text );
871 }
872
873 if ( !$title ) {
875 }
876 $newRel = Parser::getExternalLinkRel( $url, $title );
877 if ( !isset( $attribs['rel'] ) || $attribs['rel'] === '' ) {
878 $attribs['rel'] = $newRel;
879 } elseif ( $newRel !== null ) {
880 // Merge the rel attributes.
881 $newRels = explode( ' ', $newRel );
882 $oldRels = explode( ' ', $attribs['rel'] );
883 $combined = array_unique( array_merge( $newRels, $oldRels ) );
884 $attribs['rel'] = implode( ' ', $combined );
885 }
886 $link = '';
887 $success = Hooks::runner()->onLinkerMakeExternalLink(
888 $url, $text, $link, $attribs, $linktype );
889 if ( !$success ) {
890 wfDebug( "Hook LinkerMakeExternalLink changed the output of link "
891 . "with url {$url} and text {$text} to {$link}" );
892 return $link;
893 }
894 $attribs['href'] = $url;
895 return Html::rawElement( 'a', $attribs, $text );
896 }
897
906 public static function userLink( $userId, $userName, $altUserName = false ) {
907 if ( $userName === '' || $userName === false || $userName === null ) {
908 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
909 'that need to be fixed?' );
910 return wfMessage( 'empty-username' )->parse();
911 }
912
913 $classes = 'mw-userlink';
914 $page = null;
915 if ( $userId == 0 ) {
916 $page = ExternalUserNames::getUserLinkTitle( $userName );
917
918 if ( ExternalUserNames::isExternal( $userName ) ) {
919 $classes .= ' mw-extuserlink';
920 } elseif ( $altUserName === false ) {
921 $altUserName = IPUtils::prettifyIP( $userName );
922 }
923 $classes .= ' mw-anonuserlink'; // Separate link class for anons (T45179)
924 } else {
925 $page = TitleValue::tryNew( NS_USER, strtr( $userName, ' ', '_' ) );
926 }
927
928 // Wrap the output with <bdi> tags for directionality isolation
929 $linkText =
930 '<bdi>' . htmlspecialchars( $altUserName !== false ? $altUserName : $userName ) . '</bdi>';
931
932 return $page
933 ? self::link( $page, $linkText, [ 'class' => $classes ] )
934 : Html::rawElement( 'span', [ 'class' => $classes ], $linkText );
935 }
936
951 public static function userToolLinks(
952 $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits = null,
953 $useParentheses = true
954 ) {
955 if ( $userText === '' ) {
956 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
957 'that need to be fixed?' );
958 return ' ' . wfMessage( 'empty-username' )->parse();
959 }
960
962 $talkable = !( $wgDisableAnonTalk && $userId == 0 );
963 $blockable = !( $flags & self::TOOL_LINKS_NOBLOCK );
964 $addEmailLink = $flags & self::TOOL_LINKS_EMAIL && $userId;
965
966 if ( $userId == 0 && ExternalUserNames::isExternal( $userText ) ) {
967 // No tools for an external user
968 return '';
969 }
970
971 $items = [];
972 if ( $talkable ) {
973 $items[] = self::userTalkLink( $userId, $userText );
974 }
975 if ( $userId ) {
976 // check if the user has an edit
977 $attribs = [];
978 $attribs['class'] = 'mw-usertoollinks-contribs';
979 if ( $redContribsWhenNoEdits ) {
980 if ( intval( $edits ) === 0 && $edits !== 0 ) {
981 $user = User::newFromId( $userId );
982 $edits = $user->getEditCount();
983 }
984 if ( $edits === 0 ) {
985 $attribs['class'] .= ' new';
986 }
987 }
988 $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText );
989
990 $items[] = self::link( $contribsPage, wfMessage( 'contribslink' )->escaped(), $attribs );
991 }
992 $user = RequestContext::getMain()->getUser();
993 $userCanBlock = MediaWikiServices::getInstance()->getPermissionManager()
994 ->userHasRight( $user, 'block' );
995 if ( $blockable && $userCanBlock ) {
996 $items[] = self::blockLink( $userId, $userText );
997 }
998
999 if ( $addEmailLink && $user->canSendEmail() ) {
1000 $items[] = self::emailLink( $userId, $userText );
1001 }
1002
1003 Hooks::runner()->onUserToolLinksEdit( $userId, $userText, $items );
1004
1005 if ( !$items ) {
1006 return '';
1007 }
1008
1009 if ( $useParentheses ) {
1010 return wfMessage( 'word-separator' )->escaped()
1011 . '<span class="mw-usertoollinks">'
1012 . wfMessage( 'parentheses' )->rawParams( $wgLang->pipeList( $items ) )->escaped()
1013 . '</span>';
1014 }
1015
1016 $tools = [];
1017 foreach ( $items as $tool ) {
1018 $tools[] = Html::rawElement( 'span', [], $tool );
1019 }
1020 return ' <span class="mw-usertoollinks mw-changeslist-links">' .
1021 implode( ' ', $tools ) . '</span>';
1022 }
1023
1033 public static function userToolLinksRedContribs(
1034 $userId, $userText, $edits = null, $useParentheses = true
1035 ) {
1036 return self::userToolLinks( $userId, $userText, true, 0, $edits, $useParentheses );
1037 }
1038
1045 public static function userTalkLink( $userId, $userText ) {
1046 if ( $userText === '' ) {
1047 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1048 'that need to be fixed?' );
1049 return wfMessage( 'empty-username' )->parse();
1050 }
1051
1052 $userTalkPage = TitleValue::tryNew( NS_USER_TALK, strtr( $userText, ' ', '_' ) );
1053 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-talk' ];
1054 $linkText = wfMessage( 'talkpagelinktext' )->escaped();
1055
1056 return $userTalkPage
1057 ? self::link( $userTalkPage, $linkText, $moreLinkAttribs )
1058 : Html::rawElement( 'span', $moreLinkAttribs, $linkText );
1059 }
1060
1067 public static function blockLink( $userId, $userText ) {
1068 if ( $userText === '' ) {
1069 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1070 'that need to be fixed?' );
1071 return wfMessage( 'empty-username' )->parse();
1072 }
1073
1074 $blockPage = SpecialPage::getTitleFor( 'Block', $userText );
1075 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-block' ];
1076
1077 return self::link( $blockPage,
1078 wfMessage( 'blocklink' )->escaped(),
1079 $moreLinkAttribs
1080 );
1081 }
1082
1088 public static function emailLink( $userId, $userText ) {
1089 if ( $userText === '' ) {
1090 wfLogWarning( __METHOD__ . ' received an empty username. Are there database errors ' .
1091 'that need to be fixed?' );
1092 return wfMessage( 'empty-username' )->parse();
1093 }
1094
1095 $emailPage = SpecialPage::getTitleFor( 'Emailuser', $userText );
1096 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-mail' ];
1097 return self::link( $emailPage,
1098 wfMessage( 'emaillink' )->escaped(),
1099 $moreLinkAttribs
1100 );
1101 }
1102
1111 public static function revUserLink( $rev, $isPublic = false ) {
1112 // TODO inject a user
1113 $user = RequestContext::getMain()->getUser();
1114
1115 if ( $rev instanceof Revision ) {
1116 wfDeprecated( __METHOD__ . ' with a Revision object', '1.35' );
1117 $revRecord = $rev->getRevisionRecord();
1118 } else {
1119 $revRecord = $rev;
1120 }
1121
1122 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) && $isPublic ) {
1123 $link = wfMessage( 'rev-deleted-user' )->escaped();
1124 } elseif ( RevisionRecord::userCanBitfield(
1125 $revRecord->getVisibility(),
1126 RevisionRecord::DELETED_USER,
1127 $user
1128 ) ) {
1129 $revUser = $revRecord->getUser( RevisionRecord::FOR_THIS_USER, $user );
1130 $link = self::userLink(
1131 $revUser ? $revUser->getId() : 0,
1132 $revUser ? $revUser->getName() : ''
1133 );
1134 } else {
1135 $link = wfMessage( 'rev-deleted-user' )->escaped();
1136 }
1137 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
1138 return '<span class="history-deleted">' . $link . '</span>';
1139 }
1140 return $link;
1141 }
1142
1152 public static function revUserTools( $rev, $isPublic = false, $useParentheses = true ) {
1153 // TODO inject a user
1154 $user = RequestContext::getMain()->getUser();
1155
1156 if ( $rev instanceof Revision ) {
1157 wfDeprecated( __METHOD__ . ' with a Revision object', '1.35' );
1158 $revRecord = $rev->getRevisionRecord();
1159 } else {
1160 $revRecord = $rev;
1161 }
1162
1163 if ( RevisionRecord::userCanBitfield(
1164 $revRecord->getVisibility(),
1165 RevisionRecord::DELETED_USER,
1166 $user
1167 ) &&
1168 ( !$revRecord->isDeleted( RevisionRecord::DELETED_USER ) || !$isPublic )
1169 ) {
1170 $revUser = $revRecord->getUser( RevisionRecord::FOR_THIS_USER, $user );
1171 $userId = $revUser ? $revUser->getId() : 0;
1172 $userText = $revUser ? $revUser->getName() : '';
1173
1174 if ( $userId || $userText !== '' ) {
1175 $link = self::userLink( $userId, $userText )
1176 . self::userToolLinks( $userId, $userText, false, 0, null,
1177 $useParentheses );
1178 }
1179 }
1180
1181 if ( !isset( $link ) ) {
1182 $link = wfMessage( 'rev-deleted-user' )->escaped();
1183 }
1184
1185 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
1186 return ' <span class="history-deleted mw-userlink">' . $link . '</span>';
1187 }
1188 return $link;
1189 }
1190
1209 public static function formatComment(
1210 $comment, $title = null, $local = false, $wikiId = null
1211 ) {
1212 # Sanitize text a bit:
1213 $comment = str_replace( "\n", " ", $comment );
1214 # Allow HTML entities (for T15815)
1215 $comment = Sanitizer::escapeHtmlAllowEntities( $comment );
1216
1217 # Render autocomments and make links:
1218 $comment = self::formatAutocomments( $comment, $title, $local, $wikiId );
1219 return self::formatLinksInComment( $comment, $title, $local, $wikiId );
1220 }
1221
1239 private static function formatAutocomments(
1240 $comment, $title = null, $local = false, $wikiId = null
1241 ) {
1242 // @todo $append here is something of a hack to preserve the status
1243 // quo. Someone who knows more about bidi and such should decide
1244 // (1) what sane rendering even *is* for an LTR edit summary on an RTL
1245 // wiki, both when autocomments exist and when they don't, and
1246 // (2) what markup will make that actually happen.
1247 $append = '';
1248 $comment = preg_replace_callback(
1249 // To detect the presence of content before or after the
1250 // auto-comment, we use capturing groups inside optional zero-width
1251 // assertions. But older versions of PCRE can't directly make
1252 // zero-width assertions optional, so wrap them in a non-capturing
1253 // group.
1254 '!(?:(?<=(.)))?/\*\s*(.*?)\s*\*/(?:(?=(.)))?!',
1255 function ( $match ) use ( $title, $local, $wikiId, &$append ) {
1256 global $wgLang;
1257
1258 // Ensure all match positions are defined
1259 $match += [ '', '', '', '' ];
1260
1261 $pre = $match[1] !== '';
1262 $auto = $match[2];
1263 $post = $match[3] !== '';
1264 $comment = null;
1265
1266 Hooks::runner()->onFormatAutocomments(
1267 $comment, $pre, $auto, $post, Title::castFromLinkTarget( $title ), $local,
1268 $wikiId );
1269
1270 if ( $comment === null ) {
1271 if ( $title ) {
1272 $section = $auto;
1273 # Remove links that a user may have manually put in the autosummary
1274 # This could be improved by copying as much of Parser::stripSectionName as desired.
1275 $section = str_replace( [
1276 '[[:',
1277 '[[',
1278 ']]'
1279 ], '', $section );
1280
1281 // We don't want any links in the auto text to be linked, but we still
1282 // want to show any [[ ]]
1283 $sectionText = str_replace( '[[', '&#91;[', $auto );
1284
1285 $section = substr( Parser::guessSectionNameFromStrippedText( $section ), 1 );
1286 if ( $section !== '' ) {
1287 if ( $local ) {
1288 $sectionTitle = new TitleValue( NS_MAIN, '', $section );
1289 } else {
1290 $sectionTitle = $title->createFragmentTarget( $section );
1291 }
1293 $sectionTitle,
1294 $wgLang->getArrow() . $wgLang->getDirMark() . $sectionText,
1295 $wikiId,
1296 'noclasses'
1297 );
1298 }
1299 }
1300 if ( $pre ) {
1301 # written summary $presep autocomment (summary /* section */)
1302 $pre = wfMessage( 'autocomment-prefix' )->inContentLanguage()->escaped();
1303 }
1304 if ( $post ) {
1305 # autocomment $postsep written summary (/* section */ summary)
1306 $auto .= wfMessage( 'colon-separator' )->inContentLanguage()->escaped();
1307 }
1308 if ( $auto ) {
1309 $auto = '<span dir="auto"><span class="autocomment">' . $auto . '</span>';
1310 $append .= '</span>';
1311 }
1312 $comment = $pre . $auto;
1313 }
1314 return $comment;
1315 },
1316 $comment
1317 );
1318 return $comment . $append;
1319 }
1320
1340 public static function formatLinksInComment(
1341 $comment, $title = null, $local = false, $wikiId = null
1342 ) {
1343 return preg_replace_callback(
1344 '/
1345 \[\[
1346 \s*+ # ignore leading whitespace, the *+ quantifier disallows backtracking
1347 :? # ignore optional leading colon
1348 ([^[\]|]+) # 1. link target; page names cannot include [, ] or |
1349 (?:\|
1350 # 2. link text
1351 # Stop matching at ]] without relying on backtracking.
1352 ((?:]?[^\]])*+)
1353 )?
1354 \]\]
1355 ([^[]*) # 3. link trail (the text up until the next link)
1356 /x',
1357 function ( $match ) use ( $title, $local, $wikiId ) {
1358 $services = MediaWikiServices::getInstance();
1359
1360 $medians = '(?:';
1361 $medians .= preg_quote(
1362 $services->getNamespaceInfo()->getCanonicalName( NS_MEDIA ), '/' );
1363 $medians .= '|';
1364 $medians .= preg_quote(
1365 $services->getContentLanguage()->getNsText( NS_MEDIA ),
1366 '/'
1367 ) . '):';
1368
1369 $comment = $match[0];
1370
1371 # fix up urlencoded title texts (copied from Parser::replaceInternalLinks)
1372 if ( strpos( $match[1], '%' ) !== false ) {
1373 $match[1] = strtr(
1374 rawurldecode( $match[1] ),
1375 [ '<' => '&lt;', '>' => '&gt;' ]
1376 );
1377 }
1378
1379 # Handle link renaming [[foo|text]] will show link as "text"
1380 if ( $match[2] != "" ) {
1381 $text = $match[2];
1382 } else {
1383 $text = $match[1];
1384 }
1385 $submatch = [];
1386 $thelink = null;
1387 if ( preg_match( '/^' . $medians . '(.*)$/i', $match[1], $submatch ) ) {
1388 # Media link; trail not supported.
1389 $linkRegexp = '/\[\[(.*?)\]\]/';
1390 $title = Title::makeTitleSafe( NS_FILE, $submatch[1] );
1391 if ( $title ) {
1392 $thelink = Linker::makeMediaLinkObj( $title, $text );
1393 }
1394 } else {
1395 # Other kind of link
1396 # Make sure its target is non-empty
1397 if ( isset( $match[1][0] ) && $match[1][0] == ':' ) {
1398 $match[1] = substr( $match[1], 1 );
1399 }
1400 if ( $match[1] !== false && $match[1] !== '' ) {
1401 if ( preg_match(
1402 $services->getContentLanguage()->linkTrail(),
1403 $match[3],
1404 $submatch
1405 ) ) {
1406 $trail = $submatch[1];
1407 } else {
1408 $trail = "";
1409 }
1410 $linkRegexp = '/\[\[(.*?)\]\]' . preg_quote( $trail, '/' ) . '/';
1411 list( $inside, $trail ) = Linker::splitTrail( $trail );
1412
1413 $linkText = $text;
1414 $linkTarget = Linker::normalizeSubpageLink( $title, $match[1], $linkText );
1415
1416 try {
1417 $target = $services->getTitleParser()->
1418 parseTitle( $linkTarget );
1419
1420 if ( $target->getText() == '' && !$target->isExternal()
1421 && !$local && $title
1422 ) {
1423 $target = $title->createFragmentTarget( $target->getFragment() );
1424 }
1425
1426 $thelink = Linker::makeCommentLink( $target, $linkText . $inside, $wikiId ) . $trail;
1427 } catch ( MalformedTitleException $e ) {
1428 // Fall through
1429 }
1430 }
1431 }
1432 if ( $thelink ) {
1433 // If the link is still valid, go ahead and replace it in!
1434 $comment = preg_replace(
1435 $linkRegexp,
1437 $comment,
1438 1
1439 );
1440 }
1441
1442 return $comment;
1443 },
1444 $comment
1445 );
1446 }
1447
1461 public static function makeCommentLink(
1462 LinkTarget $linkTarget, $text, $wikiId = null, $options = []
1463 ) {
1464 if ( $wikiId !== null && !$linkTarget->isExternal() ) {
1465 $link = self::makeExternalLink(
1466 WikiMap::getForeignURL(
1467 $wikiId,
1468 $linkTarget->getNamespace() === 0
1469 ? $linkTarget->getDBkey()
1470 : MediaWikiServices::getInstance()->getNamespaceInfo()->
1471 getCanonicalName( $linkTarget->getNamespace() ) .
1472 ':' . $linkTarget->getDBkey(),
1473 $linkTarget->getFragment()
1474 ),
1475 $text,
1476 /* escape = */ false // Already escaped
1477 );
1478 } else {
1479 $link = self::link( $linkTarget, $text, [], [], $options );
1480 }
1481
1482 return $link;
1483 }
1484
1491 public static function normalizeSubpageLink( $contextTitle, $target, &$text ) {
1492 # Valid link forms:
1493 # Foobar -- normal
1494 # :Foobar -- override special treatment of prefix (images, language links)
1495 # /Foobar -- convert to CurrentPage/Foobar
1496 # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial and final / from text
1497 # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage
1498 # ../Foobar -- convert to CurrentPage/Foobar,
1499 # (from CurrentPage/CurrentSubPage)
1500 # ../Foobar/ -- convert to CurrentPage/Foobar, use 'Foobar' as text
1501 # (from CurrentPage/CurrentSubPage)
1502
1503 $ret = $target; # default return value is no change
1504
1505 # Some namespaces don't allow subpages,
1506 # so only perform processing if subpages are allowed
1507 if (
1508 $contextTitle && MediaWikiServices::getInstance()->getNamespaceInfo()->
1509 hasSubpages( $contextTitle->getNamespace() )
1510 ) {
1511 $hash = strpos( $target, '#' );
1512 if ( $hash !== false ) {
1513 $suffix = substr( $target, $hash );
1514 $target = substr( $target, 0, $hash );
1515 } else {
1516 $suffix = '';
1517 }
1518 # T9425
1519 $target = trim( $target );
1520 $contextPrefixedText = MediaWikiServices::getInstance()->getTitleFormatter()->
1521 getPrefixedText( $contextTitle );
1522 # Look at the first character
1523 if ( $target != '' && $target[0] === '/' ) {
1524 # / at end means we don't want the slash to be shown
1525 $m = [];
1526 $trailingSlashes = preg_match_all( '%(/+)$%', $target, $m );
1527 if ( $trailingSlashes ) {
1528 $noslash = $target = substr( $target, 1, -strlen( $m[0][0] ) );
1529 } else {
1530 $noslash = substr( $target, 1 );
1531 }
1532
1533 $ret = $contextPrefixedText . '/' . trim( $noslash ) . $suffix;
1534 if ( $text === '' ) {
1535 $text = $target . $suffix;
1536 } # this might be changed for ugliness reasons
1537 } else {
1538 # check for .. subpage backlinks
1539 $dotdotcount = 0;
1540 $nodotdot = $target;
1541 while ( strncmp( $nodotdot, "../", 3 ) == 0 ) {
1542 ++$dotdotcount;
1543 $nodotdot = substr( $nodotdot, 3 );
1544 }
1545 if ( $dotdotcount > 0 ) {
1546 $exploded = explode( '/', $contextPrefixedText );
1547 if ( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page
1548 $ret = implode( '/', array_slice( $exploded, 0, -$dotdotcount ) );
1549 # / at the end means don't show full path
1550 if ( substr( $nodotdot, -1, 1 ) === '/' ) {
1551 $nodotdot = rtrim( $nodotdot, '/' );
1552 if ( $text === '' ) {
1553 $text = $nodotdot . $suffix;
1554 }
1555 }
1556 $nodotdot = trim( $nodotdot );
1557 if ( $nodotdot != '' ) {
1558 $ret .= '/' . $nodotdot;
1559 }
1560 $ret .= $suffix;
1561 }
1562 }
1563 }
1564 }
1565
1566 return $ret;
1567 }
1568
1584 public static function commentBlock(
1585 $comment, $title = null, $local = false, $wikiId = null, $useParentheses = true
1586 ) {
1587 // '*' used to be the comment inserted by the software way back
1588 // in antiquity in case none was provided, here for backwards
1589 // compatibility, acc. to brion -ævar
1590 if ( $comment == '' || $comment == '*' ) {
1591 return '';
1592 }
1593 $formatted = self::formatComment( $comment, $title, $local, $wikiId );
1594 if ( $useParentheses ) {
1595 $formatted = wfMessage( 'parentheses' )->rawParams( $formatted )->escaped();
1596 $classNames = 'comment';
1597 } else {
1598 $classNames = 'comment comment--without-parentheses';
1599 }
1600 return " <span class=\"$classNames\">$formatted</span>";
1601 }
1602
1615 public static function revComment( $rev, $local = false, $isPublic = false,
1616 $useParentheses = true
1617 ) {
1618 // TODO inject a user
1619 $user = RequestContext::getMain()->getUser();
1620
1621 if ( $rev instanceof Revision ) {
1622 wfDeprecated( __METHOD__ . ' with a Revision object', '1.35' );
1623 $revRecord = $rev->getRevisionRecord();
1624 } else {
1625 $revRecord = $rev;
1626 }
1627
1628 if ( $revRecord->getComment( RevisionRecord::RAW ) === null ) {
1629 return "";
1630 }
1631 if ( $revRecord->isDeleted( RevisionRecord::DELETED_COMMENT ) && $isPublic ) {
1632 $block = " <span class=\"comment\">" . wfMessage( 'rev-deleted-comment' )->escaped() . "</span>";
1633 } elseif ( RevisionRecord::userCanBitfield(
1634 $revRecord->getVisibility(),
1635 RevisionRecord::DELETED_COMMENT,
1636 $user
1637 ) ) {
1638 $comment = $revRecord->getComment( RevisionRecord::FOR_THIS_USER, $user );
1639 $block = self::commentBlock(
1640 $comment ? $comment->text : null,
1641 $revRecord->getPageAsLinkTarget(),
1642 $local,
1643 null,
1644 $useParentheses
1645 );
1646 } else {
1647 $block = " <span class=\"comment\">" . wfMessage( 'rev-deleted-comment' )->escaped() . "</span>";
1648 }
1649 if ( $revRecord->isDeleted( RevisionRecord::DELETED_COMMENT ) ) {
1650 return " <span class=\"history-deleted comment\">$block</span>";
1651 }
1652 return $block;
1653 }
1654
1660 public static function formatRevisionSize( $size ) {
1661 if ( $size == 0 ) {
1662 $stxt = wfMessage( 'historyempty' )->escaped();
1663 } else {
1664 $stxt = wfMessage( 'nbytes' )->numParams( $size )->escaped();
1665 }
1666 return "<span class=\"history-size mw-diff-bytes\">$stxt</span>";
1667 }
1668
1675 public static function tocIndent() {
1676 return "\n<ul>\n";
1677 }
1678
1686 public static function tocUnindent( $level ) {
1687 return "</li>\n" . str_repeat( "</ul>\n</li>\n", $level > 0 ? $level : 0 );
1688 }
1689
1701 public static function tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex = false ) {
1702 $classes = "toclevel-$level";
1703 if ( $sectionIndex !== false ) {
1704 $classes .= " tocsection-$sectionIndex";
1705 }
1706
1707 // <li class="$classes"><a href="#$anchor"><span class="tocnumber">
1708 // $tocnumber</span> <span class="toctext">$tocline</span></a>
1709 return Html::openElement( 'li', [ 'class' => $classes ] )
1710 . Html::rawElement( 'a',
1711 [ 'href' => "#$anchor" ],
1712 Html::element( 'span', [ 'class' => 'tocnumber' ], $tocnumber )
1713 . ' '
1714 . Html::rawElement( 'span', [ 'class' => 'toctext' ], $tocline )
1715 );
1716 }
1717
1725 public static function tocLineEnd() {
1726 return "</li>\n";
1727 }
1728
1737 public static function tocList( $toc, Language $lang = null ) {
1738 $lang = $lang ?? RequestContext::getMain()->getLanguage();
1739
1740 $title = wfMessage( 'toc' )->inLanguage( $lang )->escaped();
1741
1742 return '<div id="toc" class="toc" role="navigation" aria-labelledby="mw-toc-heading">'
1743 . Html::element( 'input', [
1744 'type' => 'checkbox',
1745 'role' => 'button',
1746 'id' => 'toctogglecheckbox',
1747 'class' => 'toctogglecheckbox',
1748 'style' => 'display:none',
1749 ] )
1750 . Html::openElement( 'div', [
1751 'class' => 'toctitle',
1752 'lang' => $lang->getHtmlCode(),
1753 'dir' => $lang->getDir(),
1754 ] )
1755 . '<h2 id="mw-toc-heading">' . $title . '</h2>'
1756 . '<span class="toctogglespan">'
1757 . Html::label( '', 'toctogglecheckbox', [
1758 'class' => 'toctogglelabel',
1759 ] )
1760 . '</span>'
1761 . "</div>\n"
1762 . $toc
1763 . "</ul>\n</div>\n";
1764 }
1765
1774 public static function generateTOC( $tree, Language $lang = null ) {
1775 $toc = '';
1776 $lastLevel = 0;
1777 foreach ( $tree as $section ) {
1778 if ( $section['toclevel'] > $lastLevel ) {
1779 $toc .= self::tocIndent();
1780 } elseif ( $section['toclevel'] < $lastLevel ) {
1781 $toc .= self::tocUnindent(
1782 $lastLevel - $section['toclevel'] );
1783 } else {
1784 $toc .= self::tocLineEnd();
1785 }
1786
1787 $toc .= self::tocLine( $section['anchor'],
1788 $section['line'], $section['number'],
1789 $section['toclevel'], $section['index'] );
1790 $lastLevel = $section['toclevel'];
1791 }
1792 $toc .= self::tocLineEnd();
1793 return self::tocList( $toc, $lang );
1794 }
1795
1812 public static function makeHeadline( $level, $attribs, $anchor, $html,
1813 $link, $fallbackAnchor = false
1814 ) {
1815 $anchorEscaped = htmlspecialchars( $anchor );
1816 $fallback = '';
1817 if ( $fallbackAnchor !== false && $fallbackAnchor !== $anchor ) {
1818 $fallbackAnchor = htmlspecialchars( $fallbackAnchor );
1819 $fallback = "<span id=\"$fallbackAnchor\"></span>";
1820 }
1821 return "<h$level$attribs"
1822 . "$fallback<span class=\"mw-headline\" id=\"$anchorEscaped\">$html</span>"
1823 . $link
1824 . "</h$level>";
1825 }
1826
1833 public static function splitTrail( $trail ) {
1834 $regex = MediaWikiServices::getInstance()->getContentLanguage()->linkTrail();
1835 $inside = '';
1836 if ( $trail !== '' && preg_match( $regex, $trail, $m ) ) {
1837 list( , $inside, $trail ) = $m;
1838 }
1839 return [ $inside, $trail ];
1840 }
1841
1871 public static function generateRollback( $rev, IContextSource $context = null,
1872 $options = [ 'verify' ]
1873 ) {
1874 if ( $rev instanceof Revision ) {
1875 wfDeprecated( __METHOD__ . ' with a Revision object', '1.35' );
1876 $revRecord = $rev->getRevisionRecord();
1877 } else {
1878 $revRecord = $rev;
1879 }
1880
1881 if ( $context === null ) {
1882 $context = RequestContext::getMain();
1883 }
1884
1885 $editCount = false;
1886 if ( in_array( 'verify', $options, true ) ) {
1887 $editCount = self::getRollbackEditCount( $revRecord, true );
1888 if ( $editCount === false ) {
1889 return '';
1890 }
1891 }
1892
1893 $inner = self::buildRollbackLink( $revRecord, $context, $editCount );
1894
1895 if ( !in_array( 'noBrackets', $options, true ) ) {
1896 $inner = $context->msg( 'brackets' )->rawParams( $inner )->escaped();
1897 }
1898
1899 if ( $context->getUser()->getBoolOption( 'showrollbackconfirmation' ) ) {
1900 $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
1901 $stats->increment( 'rollbackconfirmation.event.load' );
1902 $context->getOutput()->addModules( 'mediawiki.misc-authed-curate' );
1903 }
1904
1905 return '<span class="mw-rollback-link">' . $inner . '</span>';
1906 }
1907
1926 public static function getRollbackEditCount( $rev, $verify ) {
1928
1929 if ( $rev instanceof Revision ) {
1930 wfDeprecated( __METHOD__ . ' with a Revision object', '1.35' );
1931 $revRecord = $rev->getRevisionRecord();
1932 } else {
1933 $revRecord = $rev;
1934 }
1935
1936 if ( !is_int( $wgShowRollbackEditCount ) || !$wgShowRollbackEditCount > 0 ) {
1937 // Nothing has happened, indicate this by returning 'null'
1938 return null;
1939 }
1940
1941 $dbr = wfGetDB( DB_REPLICA );
1942
1943 // Up to the value of $wgShowRollbackEditCount revisions are counted
1944 $revQuery = MediaWikiServices::getInstance()->getRevisionStore()->getQueryInfo();
1945 $res = $dbr->select(
1946 $revQuery['tables'],
1947 [ 'rev_user_text' => $revQuery['fields']['rev_user_text'], 'rev_deleted' ],
1948 [ 'rev_page' => $revRecord->getPageId() ],
1949 __METHOD__,
1950 [
1951 'USE INDEX' => [ 'revision' => 'page_timestamp' ],
1952 'ORDER BY' => 'rev_timestamp DESC',
1953 'LIMIT' => $wgShowRollbackEditCount + 1
1954 ],
1955 $revQuery['joins']
1956 );
1957
1958 $revUser = $revRecord->getUser( RevisionRecord::RAW );
1959 $revUserText = $revUser ? $revUser->getName() : '';
1960
1961 $editCount = 0;
1962 $moreRevs = false;
1963 foreach ( $res as $row ) {
1964 if ( $row->rev_user_text != $revUserText ) {
1965 if ( $verify &&
1966 ( $row->rev_deleted & RevisionRecord::DELETED_TEXT
1967 || $row->rev_deleted & RevisionRecord::DELETED_USER
1968 ) ) {
1969 // If the user or the text of the revision we might rollback
1970 // to is deleted in some way we can't rollback. Similar to
1971 // the sanity checks in WikiPage::commitRollback.
1972 return false;
1973 }
1974 $moreRevs = true;
1975 break;
1976 }
1977 $editCount++;
1978 }
1979
1980 if ( $verify && $editCount <= $wgShowRollbackEditCount && !$moreRevs ) {
1981 // We didn't find at least $wgShowRollbackEditCount revisions made by the current user
1982 // and there weren't any other revisions. That means that the current user is the only
1983 // editor, so we can't rollback
1984 return false;
1985 }
1986 return $editCount;
1987 }
1988
2003 public static function buildRollbackLink( $rev, IContextSource $context = null,
2004 $editCount = false
2005 ) {
2007
2008 if ( $rev instanceof Revision ) {
2009 wfDeprecated( __METHOD__ . ' with a Revision object', '1.35' );
2010 $revRecord = $rev->getRevisionRecord();
2011 } else {
2012 $revRecord = $rev;
2013 }
2014
2015 // To config which pages are affected by miser mode
2016 $disableRollbackEditCountSpecialPage = [ 'Recentchanges', 'Watchlist' ];
2017
2018 if ( $context === null ) {
2019 $context = RequestContext::getMain();
2020 }
2021
2022 $title = $revRecord->getPageAsLinkTarget();
2023 $revUser = $revRecord->getUser();
2024 $revUserText = $revUser ? $revUser->getName() : '';
2025
2026 $query = [
2027 'action' => 'rollback',
2028 'from' => $revUserText,
2029 'token' => $context->getUser()->getEditToken( 'rollback' ),
2030 ];
2031
2032 $attrs = [
2033 'data-mw' => 'interface',
2034 'title' => $context->msg( 'tooltip-rollback' )->text()
2035 ];
2036
2037 $options = [ 'known', 'noclasses' ];
2038
2039 if ( $context->getRequest()->getBool( 'bot' ) ) {
2040 // T17999
2041 $query['hidediff'] = '1';
2042 $query['bot'] = '1';
2043 }
2044
2045 $disableRollbackEditCount = false;
2046 if ( $wgMiserMode ) {
2047 foreach ( $disableRollbackEditCountSpecialPage as $specialPage ) {
2048 if ( $context->getTitle()->isSpecial( $specialPage ) ) {
2049 $disableRollbackEditCount = true;
2050 break;
2051 }
2052 }
2053 }
2054
2055 if ( !$disableRollbackEditCount
2056 && is_int( $wgShowRollbackEditCount )
2058 ) {
2059 if ( !is_numeric( $editCount ) ) {
2060 $editCount = self::getRollbackEditCount( $revRecord, false );
2061 }
2062
2063 if ( $editCount > $wgShowRollbackEditCount ) {
2064 $html = $context->msg( 'rollbacklinkcount-morethan' )
2065 ->numParams( $wgShowRollbackEditCount )->parse();
2066 } else {
2067 $html = $context->msg( 'rollbacklinkcount' )->numParams( $editCount )->parse();
2068 }
2069
2070 return self::link( $title, $html, $attrs, $query, $options );
2071 }
2072
2073 $html = $context->msg( 'rollbacklink' )->escaped();
2074 return self::link( $title, $html, $attrs, $query, $options );
2075 }
2076
2085 public static function formatHiddenCategories( $hiddencats ) {
2086 $outText = '';
2087 if ( count( $hiddencats ) > 0 ) {
2088 # Construct the HTML
2089 $outText = '<div class="mw-hiddenCategoriesExplanation">';
2090 $outText .= wfMessage( 'hiddencategories' )->numParams( count( $hiddencats ) )->parseAsBlock();
2091 $outText .= "</div><ul>\n";
2092
2093 foreach ( $hiddencats as $titleObj ) {
2094 # If it's hidden, it must exist - no need to check with a LinkBatch
2095 $outText .= '<li>'
2096 . self::link( $titleObj, null, [], [], 'known' )
2097 . "</li>\n";
2098 }
2099 $outText .= '</ul>';
2100 }
2101 return $outText;
2102 }
2103
2120 public static function titleAttrib( $name, $options = null, array $msgParams = [] ) {
2121 $message = wfMessage( "tooltip-$name", $msgParams );
2122 if ( !$message->exists() ) {
2123 $tooltip = false;
2124 } else {
2125 $tooltip = $message->text();
2126 # Compatibility: formerly some tooltips had [alt-.] hardcoded
2127 $tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip );
2128 # Message equal to '-' means suppress it.
2129 if ( $tooltip == '-' ) {
2130 $tooltip = false;
2131 }
2132 }
2133
2134 $options = (array)$options;
2135
2136 if ( in_array( 'nonexisting', $options ) ) {
2137 $tooltip = wfMessage( 'red-link-title', $tooltip ?: '' )->text();
2138 }
2139 if ( in_array( 'withaccess', $options ) ) {
2140 $accesskey = self::accesskey( $name );
2141 if ( $accesskey !== false ) {
2142 // Should be build the same as in jquery.accessKeyLabel.js
2143 if ( $tooltip === false || $tooltip === '' ) {
2144 $tooltip = wfMessage( 'brackets', $accesskey )->text();
2145 } else {
2146 $tooltip .= wfMessage( 'word-separator' )->text();
2147 $tooltip .= wfMessage( 'brackets', $accesskey )->text();
2148 }
2149 }
2150 }
2151
2152 return $tooltip;
2153 }
2154
2155 public static $accesskeycache;
2156
2168 public static function accesskey( $name ) {
2169 if ( isset( self::$accesskeycache[$name] ) ) {
2170 return self::$accesskeycache[$name];
2171 }
2172
2173 $message = wfMessage( "accesskey-$name" );
2174
2175 if ( !$message->exists() ) {
2176 $accesskey = false;
2177 } else {
2178 $accesskey = $message->plain();
2179 if ( $accesskey === '' || $accesskey === '-' ) {
2180 # @todo FIXME: Per standard MW behavior, a value of '-' means to suppress the
2181 # attribute, but this is broken for accesskey: that might be a useful
2182 # value.
2183 $accesskey = false;
2184 }
2185 }
2186
2187 self::$accesskeycache[$name] = $accesskey;
2188 return self::$accesskeycache[$name];
2189 }
2190
2205 public static function getRevDeleteLink( User $user, $rev, LinkTarget $title ) {
2206 if ( $rev instanceof Revision ) {
2207 wfDeprecated( __METHOD__ . ' with a Revision object', '1.35' );
2208 $revRecord = $rev->getRevisionRecord();
2209 } else {
2210 $revRecord = $rev;
2211 }
2212
2213 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
2214 $canHide = $permissionManager->userHasRight( $user, 'deleterevision' );
2215 $canHideHistory = $permissionManager->userHasRight( $user, 'deletedhistory' );
2216 if ( !$canHide && !( $revRecord->getVisibility() && $canHideHistory ) ) {
2217 return '';
2218 }
2219
2220 if ( !RevisionRecord::userCanBitfield(
2221 $revRecord->getVisibility(),
2222 RevisionRecord::DELETED_RESTRICTED,
2223 $user
2224 ) ) {
2225 return self::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
2226 }
2227 $prefixedDbKey = MediaWikiServices::getInstance()->getTitleFormatter()->
2228 getPrefixedDBkey( $title );
2229 if ( $revRecord->getId() ) {
2230 // RevDelete links using revision ID are stable across
2231 // page deletion and undeletion; use when possible.
2232 $query = [
2233 'type' => 'revision',
2234 'target' => $prefixedDbKey,
2235 'ids' => $revRecord->getId()
2236 ];
2237 } else {
2238 // Older deleted entries didn't save a revision ID.
2239 // We have to refer to these by timestamp, ick!
2240 $query = [
2241 'type' => 'archive',
2242 'target' => $prefixedDbKey,
2243 'ids' => $revRecord->getTimestamp()
2244 ];
2245 }
2246 return self::revDeleteLink(
2247 $query,
2248 $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ),
2249 $canHide
2250 );
2251 }
2252
2263 public static function revDeleteLink( $query = [], $restricted = false, $delete = true ) {
2264 $sp = SpecialPage::getTitleFor( 'Revisiondelete' );
2265 $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
2266 $html = wfMessage( $msgKey )->escaped();
2267 $tag = $restricted ? 'strong' : 'span';
2268 $link = self::link( $sp, $html, [], $query, [ 'known', 'noclasses' ] );
2269 return Xml::tags(
2270 $tag,
2271 [ 'class' => 'mw-revdelundel-link' ],
2272 wfMessage( 'parentheses' )->rawParams( $link )->escaped()
2273 );
2274 }
2275
2285 public static function revDeleteLinkDisabled( $delete = true ) {
2286 $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
2287 $html = wfMessage( $msgKey )->escaped();
2288 $htmlParentheses = wfMessage( 'parentheses' )->rawParams( $html )->escaped();
2289 return Xml::tags( 'span', [ 'class' => 'mw-revdelundel-link' ], $htmlParentheses );
2290 }
2291
2304 public static function tooltipAndAccesskeyAttribs(
2305 $name,
2306 array $msgParams = [],
2307 $options = null
2308 ) {
2309 $options = (array)$options;
2310 $options[] = 'withaccess';
2311 $tooltipTitle = $name;
2312
2313 // @since 1.35 - add a WatchlistExpiry feature flag to show new watchstar tooltip message
2314 $skin = RequestContext::getMain()->getSkin();
2315 $isWatchlistExpiryEnabled = $skin->getConfig()->get( 'WatchlistExpiry' );
2316 if ( $name === 'ca-unwatch' && $isWatchlistExpiryEnabled ) {
2317 $watchStore = MediaWikiServices::getInstance()->getWatchedItemStore();
2318 $watchedItem = $watchStore->getWatchedItem( $skin->getUser(),
2319 $skin->getRelevantTitle() );
2320 if ( $watchedItem instanceof WatchedItem && $watchedItem->getExpiry() !== null ) {
2321 $diffInDays = $watchedItem->getExpiryInDays();
2322
2323 if ( $diffInDays ) {
2324 $msgParams = [ $diffInDays ];
2325 // Resolves to tooltip-ca-unwatch-expiring message
2326 $tooltipTitle = 'ca-unwatch-expiring';
2327 } else { // Resolves to tooltip-ca-unwatch-expiring-hours message
2328 $tooltipTitle = 'ca-unwatch-expiring-hours';
2329 }
2330
2331 }
2332 }
2333
2334 $attribs = [
2335 'title' => self::titleAttrib( $tooltipTitle, $options, $msgParams ),
2336 'accesskey' => self::accesskey( $name )
2337 ];
2338 if ( $attribs['title'] === false ) {
2339 unset( $attribs['title'] );
2340 }
2341 if ( $attribs['accesskey'] === false ) {
2342 unset( $attribs['accesskey'] );
2343 }
2344 return $attribs;
2345 }
2346
2354 public static function tooltip( $name, $options = null ) {
2355 $tooltip = self::titleAttrib( $name, $options );
2356 if ( $tooltip === false ) {
2357 return '';
2358 }
2359 return Xml::expandAttributes( [
2360 'title' => $tooltip
2361 ] );
2362 }
2363
2364}
$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:1871
static makeMediaLinkFile(LinkTarget $title, $file, $html='')
Create a direct link to a given uploaded file.
Definition Linker.php:788
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:1701
static userLink( $userId, $userName, $altUserName=false)
Make user link (or user contributions for unregistered users)
Definition Linker.php:906
static $accesskeycache
Definition Linker.php:2155
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:2120
static accesskey( $name)
Given the id of an interface element, constructs the appropriate accesskey attribute from the system ...
Definition Linker.php:2168
static formatAutocomments( $comment, $title=null, $local=false, $wikiId=null)
Converts autogenerated comments in edit summaries into section links.
Definition Linker.php:1239
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:830
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:2003
static makeCommentLink(LinkTarget $linkTarget, $text, $wikiId=null, $options=[])
Generates a link to the given LinkTarget.
Definition Linker.php:1461
static processResponsiveImages( $file, $thumb, $hp)
Process responsive images: add 1.5x and 2x subimages to the thumbnail, where applicable.
Definition Linker.php:649
static blockLink( $userId, $userText)
Definition Linker.php:1067
static revDeleteLinkDisabled( $delete=true)
Creates a dead (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2285
static getRollbackEditCount( $rev, $verify)
This function will return the number of revisions which a rollback would revert and,...
Definition Linker.php:1926
static normalizeSubpageLink( $contextTitle, $target, &$text)
Definition Linker.php:1491
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:740
static tocIndent()
Add another level to the Table of Contents.
Definition Linker.php:1675
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:1111
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:1615
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:1088
static formatHiddenCategories( $hiddencats)
Returns HTML for the "hidden categories on this page" list.
Definition Linker.php:2085
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:1833
static makeThumbLink2(LinkTarget $title, $file, $frameParams=[], $handlerParams=[], $time=false, $query="", ?Parser $parser=null)
Definition Linker.php:520
static generateTOC( $tree, Language $lang=null)
Generate a table of contents from a section tree.
Definition Linker.php:1774
const TOOL_LINKS_EMAIL
Definition Linker.php:41
static formatRevisionSize( $size)
Definition Linker.php:1660
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:1584
static tooltip( $name, $options=null)
Returns raw bits of HTML, use titleAttrib()
Definition Linker.php:2354
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:1152
static userTalkLink( $userId, $userText)
Definition Linker.php:1045
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:2205
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:1340
static makeMediaLinkObj( $title, $html='', $time=false)
Create a direct link to a given uploaded file.
Definition Linker.php:769
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:493
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition Linker.php:856
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:951
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:1812
static tocUnindent( $level)
Finish one or more sublevels on the Table of Contents.
Definition Linker.php:1686
static tooltipAndAccesskeyAttribs( $name, array $msgParams=[], $options=null)
Returns the attributes for the tooltip and access key.
Definition Linker.php:2304
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:1737
static getImageLinkMTOParams( $frameParams, $query='', $parser=null)
Get the link parameters for MediaTransformOutput::toHtml() from given frame parameters supplied by th...
Definition Linker.php:453
static revDeleteLink( $query=[], $restricted=false, $delete=true)
Creates a (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2263
static tocLineEnd()
End a Table Of Contents line.
Definition Linker.php:1725
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:1209
static userToolLinksRedContribs( $userId, $userText, $edits=null, $useParentheses=true)
Alias for userToolLinks( $userId, $userText, true );.
Definition Linker.php:1033
static makeBrokenImageLinkObj( $title, $label='', $query='', $unused1='', $unused2='', $time=false)
Make a "broken" link to an image.
Definition Linker.php:684
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.
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition Parser.php:85
getTargetLanguage()
Get the target language for the content being parsed.
Definition Parser.php:1124
getTitle()
Definition Parser.php:1007
getBadFileLookup()
Get the BadFileLookup instance that this Parser is using.
Definition Parser.php:1205
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 escapeRegexReplacement( $string)
Escape a string to make it suitable for inclusion in a preg_replace() replacement parameter.
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
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition User.php:565
static getDefaultOption( $opt)
Get a given default option value.
Definition User.php:1553
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