MediaWiki REL1_39
Linker.php
Go to the documentation of this file.
1<?php
23use HtmlFormatter\HtmlFormatter;
29use Wikimedia\Assert\Assert;
30use Wikimedia\IPUtils;
32
42class Linker {
46 public const TOOL_LINKS_NOBLOCK = 1;
47 public const TOOL_LINKS_EMAIL = 2;
48
91 public static function link(
92 $target, $html = null, $customAttribs = [], $query = [], $options = []
93 ) {
94 if ( !$target instanceof LinkTarget ) {
95 wfWarn( __METHOD__ . ': Requires $target to be a LinkTarget object.', 2 );
96 return "<!-- ERROR -->$html";
97 }
98
99 $services = MediaWikiServices::getInstance();
100 $options = (array)$options;
101 if ( $options ) {
102 // Custom options, create new LinkRenderer
103 $linkRenderer = $services->getLinkRendererFactory()
104 ->createFromLegacyOptions( $options );
105 } else {
106 $linkRenderer = $services->getLinkRenderer();
107 }
108
109 if ( $html !== null ) {
110 $text = new HtmlArmor( $html );
111 } else {
112 $text = null;
113 }
114
115 if ( in_array( 'known', $options, true ) ) {
116 return $linkRenderer->makeKnownLink( $target, $text, $customAttribs, $query );
117 }
118
119 if ( in_array( 'broken', $options, true ) ) {
120 return $linkRenderer->makeBrokenLink( $target, $text, $customAttribs, $query );
121 }
122
123 if ( in_array( 'noclasses', $options, true ) ) {
124 return $linkRenderer->makePreloadedLink( $target, $text, '', $customAttribs, $query );
125 }
126
127 return $linkRenderer->makeLink( $target, $text, $customAttribs, $query );
128 }
129
143 public static function linkKnown(
144 $target, $html = null, $customAttribs = [],
145 $query = [], $options = [ 'known' ]
146 ) {
147 return self::link( $target, $html, $customAttribs, $query, $options );
148 }
149
165 public static function makeSelfLinkObj( $nt, $html = '', $query = '', $trail = '', $prefix = '' ) {
166 $nt = Title::newFromLinkTarget( $nt );
167 $ret = "<a class=\"mw-selflink selflink\">{$prefix}{$html}</a>{$trail}";
168 if ( !Hooks::runner()->onSelfLinkBegin( $nt, $html, $trail, $prefix, $ret ) ) {
169 return $ret;
170 }
171
172 if ( $html == '' ) {
173 $html = htmlspecialchars( $nt->getPrefixedText() );
174 }
175 [ $inside, $trail ] = self::splitTrail( $trail );
176 return "<a class=\"mw-selflink selflink\">{$prefix}{$html}{$inside}</a>{$trail}";
177 }
178
189 public static function getInvalidTitleDescription( IContextSource $context, $namespace, $title ) {
190 // First we check whether the namespace exists or not.
191 if ( MediaWikiServices::getInstance()->getNamespaceInfo()->exists( $namespace ) ) {
192 if ( $namespace == NS_MAIN ) {
193 $name = $context->msg( 'blanknamespace' )->text();
194 } else {
195 $name = MediaWikiServices::getInstance()->getContentLanguage()->
196 getFormattedNsText( $namespace );
197 }
198 return $context->msg( 'invalidtitle-knownnamespace', $namespace, $name, $title )->text();
199 }
200
201 return $context->msg( 'invalidtitle-unknownnamespace', $namespace, $title )->text();
202 }
203
210 public static function normaliseSpecialPage( LinkTarget $target ) {
211 wfDeprecated( __METHOD__, '1.35' );
212 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
213 return $linkRenderer->normalizeTarget( $target );
214 }
215
224 private static function fnamePart( $url ) {
225 $basename = strrchr( $url, '/' );
226 if ( $basename === false ) {
227 $basename = $url;
228 } else {
229 $basename = substr( $basename, 1 );
230 }
231 return $basename;
232 }
233
244 public static function makeExternalImage( $url, $alt = '' ) {
245 if ( $alt == '' ) {
246 $alt = self::fnamePart( $url );
247 }
248 $img = '';
249 $success = Hooks::runner()->onLinkerMakeExternalImage( $url, $alt, $img );
250 if ( !$success ) {
251 wfDebug( "Hook LinkerMakeExternalImage changed the output of external image "
252 . "with url {$url} and alt text {$alt} to {$img}" );
253 return $img;
254 }
255 return Html::element( 'img',
256 [
257 'src' => $url,
258 'alt' => $alt
259 ]
260 );
261 }
262
301 public static function makeImageLink( Parser $parser, LinkTarget $title,
302 $file, $frameParams = [], $handlerParams = [], $time = false,
303 $query = '', $widthOption = null
304 ) {
305 $title = Title::newFromLinkTarget( $title );
306 $res = null;
307 $dummy = new DummyLinker;
308 if ( !Hooks::runner()->onImageBeforeProduceHTML( $dummy, $title,
309 // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
310 $file, $frameParams, $handlerParams, $time, $res,
311 // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
312 $parser, $query, $widthOption )
313 ) {
314 return $res;
315 }
316
317 if ( $file && !$file->allowInlineDisplay() ) {
318 wfDebug( __METHOD__ . ': ' . $title->getPrefixedDBkey() . ' does not allow inline display' );
319 return self::link( $title );
320 }
321
322 // Clean up parameters
323 $page = $handlerParams['page'] ?? false;
324 if ( !isset( $frameParams['align'] ) ) {
325 $frameParams['align'] = '';
326 }
327 if ( !isset( $frameParams['alt'] ) ) {
328 $frameParams['alt'] = '';
329 }
330 if ( !isset( $frameParams['title'] ) ) {
331 $frameParams['title'] = '';
332 }
333 if ( !isset( $frameParams['class'] ) ) {
334 $frameParams['class'] = '';
335 }
336
337 $services = MediaWikiServices::getInstance();
338 $config = $services->getMainConfig();
339 $enableLegacyMediaDOM = $config->get( MainConfigNames::ParserEnableLegacyMediaDOM );
340
341 $classes = [];
342 if (
343 !isset( $handlerParams['width'] ) &&
344 !isset( $frameParams['manualthumb'] ) &&
345 !isset( $frameParams['framed'] )
346 ) {
347 $classes[] = 'mw-default-size';
348 }
349
350 $prefix = $postfix = '';
351
352 if ( $enableLegacyMediaDOM ) {
353 if ( $frameParams['align'] == 'center' ) {
354 $prefix = '<div class="center">';
355 $postfix = '</div>';
356 $frameParams['align'] = 'none';
357 }
358 }
359
360 if ( $file && !isset( $handlerParams['width'] ) ) {
361 if ( isset( $handlerParams['height'] ) && $file->isVectorized() ) {
362 // If its a vector image, and user only specifies height
363 // we don't want it to be limited by its "normal" width.
364 $svgMaxSize = $config->get( MainConfigNames::SVGMaxSize );
365 $handlerParams['width'] = $svgMaxSize;
366 } else {
367 $handlerParams['width'] = $file->getWidth( $page );
368 }
369
370 if ( isset( $frameParams['thumbnail'] )
371 || isset( $frameParams['manualthumb'] )
372 || isset( $frameParams['framed'] )
373 || isset( $frameParams['frameless'] )
374 || !$handlerParams['width']
375 ) {
376 $thumbLimits = $config->get( MainConfigNames::ThumbLimits );
377 $thumbUpright = $config->get( MainConfigNames::ThumbUpright );
378 if ( $widthOption === null || !isset( $thumbLimits[$widthOption] ) ) {
379 $userOptionsLookup = $services->getUserOptionsLookup();
380 $widthOption = $userOptionsLookup->getDefaultOption( 'thumbsize' );
381 }
382
383 // Reduce width for upright images when parameter 'upright' is used
384 if ( isset( $frameParams['upright'] ) && $frameParams['upright'] == 0 ) {
385 $frameParams['upright'] = $thumbUpright;
386 }
387
388 // For caching health: If width scaled down due to upright
389 // parameter, round to full __0 pixel to avoid the creation of a
390 // lot of odd thumbs.
391 $prefWidth = isset( $frameParams['upright'] ) ?
392 round( $thumbLimits[$widthOption] * $frameParams['upright'], -1 ) :
393 $thumbLimits[$widthOption];
394
395 // Use width which is smaller: real image width or user preference width
396 // Unless image is scalable vector.
397 if ( !isset( $handlerParams['height'] ) && ( $handlerParams['width'] <= 0 ||
398 $prefWidth < $handlerParams['width'] || $file->isVectorized() ) ) {
399 $handlerParams['width'] = $prefWidth;
400 }
401 }
402 }
403
404 // Parser::makeImage has a similarly named variable
405 $hasVisibleCaption = isset( $frameParams['thumbnail'] ) ||
406 isset( $frameParams['manualthumb'] ) ||
407 isset( $frameParams['framed'] );
408
409 if ( $hasVisibleCaption ) {
410 if ( $enableLegacyMediaDOM ) {
411 // This is no longer needed in our new media output, since the
412 // default styling in content.media-common.less takes care of it;
413 // see T269704.
414
415 # Create a thumbnail. Alignment depends on the writing direction of
416 # the page content language (right-aligned for LTR languages,
417 # left-aligned for RTL languages)
418 # If a thumbnail width has not been provided, it is set
419 # to the default user option as specified in Language*.php
420 if ( $frameParams['align'] == '' ) {
421 $frameParams['align'] = $parser->getTargetLanguage()->alignEnd();
422 }
423 }
424 return $prefix . self::makeThumbLink2(
425 $title, $file, $frameParams, $handlerParams, $time, $query,
426 $classes, $parser
427 ) . $postfix;
428 }
429
430 $rdfaType = 'mw:File';
431
432 if ( isset( $frameParams['frameless'] ) ) {
433 $rdfaType .= '/Frameless';
434 if ( $file ) {
435 $srcWidth = $file->getWidth( $page );
436 # For "frameless" option: do not present an image bigger than the
437 # source (for bitmap-style images). This is the same behavior as the
438 # "thumb" option does it already.
439 if ( $srcWidth && !$file->mustRender() && $handlerParams['width'] > $srcWidth ) {
440 $handlerParams['width'] = $srcWidth;
441 }
442 }
443 }
444
445 if ( $file && isset( $handlerParams['width'] ) ) {
446 # Create a resized image, without the additional thumbnail features
447 $thumb = $file->transform( $handlerParams );
448 } else {
449 $thumb = false;
450 }
451
452 $isBadFile = $file && $thumb &&
453 $parser->getBadFileLookup()->isBadFile( $title->getDBkey(), $parser->getTitle() );
454
455 if ( !$thumb || ( !$enableLegacyMediaDOM && $thumb->isError() ) || $isBadFile ) {
456 $rdfaType = 'mw:Error ' . $rdfaType;
457 $currentExists = $file && $file->exists();
458 if ( $enableLegacyMediaDOM ) {
459 // This is the information for tooltips for inline images which
460 // Parsoid stores in data-mw. See T273014
461 $label = $frameParams['title'];
462 } else {
463 if ( $currentExists && !$thumb ) {
464 $label = wfMessage( 'thumbnail_error', '' )->text();
465 } elseif ( $thumb && $thumb->isError() ) {
466 Assert::invariant(
467 $thumb instanceof MediaTransformError,
468 'Unknown MediaTransformOutput: ' . get_class( $thumb )
469 );
470 $label = $thumb->toText();
471 } else {
472 $label = '';
473 }
474 }
476 $title, $label, '', '', '', (bool)$time, $handlerParams, $currentExists
477 );
478 } else {
479 self::processResponsiveImages( $file, $thumb, $handlerParams );
480 $params = [
481 'alt' => $frameParams['alt'],
482 'title' => $frameParams['title'],
483 ];
484 if ( $enableLegacyMediaDOM ) {
485 $params += [
486 'valign' => $frameParams['valign'] ?? false,
487 'img-class' => $frameParams['class'],
488 ];
489 if ( isset( $frameParams['border'] ) ) {
490 $params['img-class'] .= ( $params['img-class'] !== '' ? ' ' : '' ) . 'thumbborder';
491 }
492 }
493 $params = self::getImageLinkMTOParams( $frameParams, $query, $parser ) + $params;
494 $s = $thumb->toHtml( $params );
495 }
496
497 if ( $enableLegacyMediaDOM ) {
498 if ( $frameParams['align'] != '' ) {
499 $s = Html::rawElement(
500 'div',
501 [ 'class' => 'float' . $frameParams['align'] ],
502 $s
503 );
504 }
505 return str_replace( "\n", ' ', $prefix . $s . $postfix );
506 }
507
508 $wrapper = 'span';
509 $caption = '';
510
511 if ( $frameParams['align'] != '' ) {
512 $wrapper = 'figure';
513 // Possible values: mw-halign-left mw-halign-center mw-halign-right mw-halign-none
514 $classes[] = "mw-halign-{$frameParams['align']}";
515 $caption = Html::rawElement(
516 'figcaption', [], $frameParams['caption'] ?? ''
517 );
518 } elseif ( isset( $frameParams['valign'] ) ) {
519 // Possible values: mw-valign-middle mw-valign-baseline mw-valign-sub
520 // mw-valign-super mw-valign-top mw-valign-text-top mw-valign-bottom
521 // mw-valign-text-bottom
522 $classes[] = "mw-valign-{$frameParams['valign']}";
523 }
524
525 if ( isset( $frameParams['border'] ) ) {
526 $classes[] = 'mw-image-border';
527 }
528
529 if ( isset( $frameParams['class'] ) ) {
530 $classes[] = $frameParams['class'];
531 }
532
533 $attribs = [
534 'class' => $classes,
535 'typeof' => $rdfaType,
536 ];
537
538 $s = Html::rawElement( $wrapper, $attribs, $s . $caption );
539
540 return str_replace( "\n", ' ', $s );
541 }
542
551 public static function getImageLinkMTOParams( $frameParams, $query = '', $parser = null ) {
552 $mtoParams = [];
553 if ( isset( $frameParams['link-url'] ) && $frameParams['link-url'] !== '' ) {
554 $mtoParams['custom-url-link'] = $frameParams['link-url'];
555 if ( isset( $frameParams['link-target'] ) ) {
556 $mtoParams['custom-target-link'] = $frameParams['link-target'];
557 }
558 if ( $parser ) {
559 $extLinkAttrs = $parser->getExternalLinkAttribs( $frameParams['link-url'] );
560 foreach ( $extLinkAttrs as $name => $val ) {
561 // Currently could include 'rel' and 'target'
562 $mtoParams['parser-extlink-' . $name] = $val;
563 }
564 }
565 } elseif ( isset( $frameParams['link-title'] ) && $frameParams['link-title'] !== '' ) {
566 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
567 $mtoParams['custom-title-link'] = Title::newFromLinkTarget(
568 $linkRenderer->normalizeTarget( $frameParams['link-title'] )
569 );
570 if ( isset( $frameParams['link-title-query'] ) ) {
571 $mtoParams['custom-title-link-query'] = $frameParams['link-title-query'];
572 }
573 } elseif ( !empty( $frameParams['no-link'] ) ) {
574 // No link
575 } else {
576 $mtoParams['desc-link'] = true;
577 $mtoParams['desc-query'] = $query;
578 }
579 return $mtoParams;
580 }
581
594 public static function makeThumbLinkObj(
595 LinkTarget $title, $file, $label = '', $alt = '', $align = null,
596 $params = [], $framed = false, $manualthumb = ''
597 ) {
598 $frameParams = [
599 'alt' => $alt,
600 'caption' => $label,
601 'align' => $align
602 ];
603 $classes = [];
604 if ( $manualthumb ) {
605 $frameParams['manualthumb'] = $manualthumb;
606 } elseif ( $framed ) {
607 $frameParams['framed'] = true;
608 } elseif ( !isset( $params['width'] ) ) {
609 $classes[] = 'mw-default-size';
610 }
612 $title, $file, $frameParams, $params, false, '', $classes
613 );
614 }
615
627 public static function makeThumbLink2(
628 LinkTarget $title, $file, $frameParams = [], $handlerParams = [],
629 $time = false, $query = '', array $classes = [], ?Parser $parser = null
630 ) {
631 $exists = $file && $file->exists();
632
633 $services = MediaWikiServices::getInstance();
634 $enableLegacyMediaDOM = $services->getMainConfig()->get( MainConfigNames::ParserEnableLegacyMediaDOM );
635
636 $page = $handlerParams['page'] ?? false;
637 if ( !isset( $frameParams['align'] ) ) {
638 $frameParams['align'] = '';
639 if ( $enableLegacyMediaDOM ) {
640 $frameParams['align'] = 'right';
641 }
642 }
643 if ( !isset( $frameParams['alt'] ) ) {
644 $frameParams['alt'] = '';
645 }
646 if ( !isset( $frameParams['caption'] ) ) {
647 $frameParams['caption'] = '';
648 }
649
650 if ( empty( $handlerParams['width'] ) ) {
651 // Reduce width for upright images when parameter 'upright' is used
652 $handlerParams['width'] = isset( $frameParams['upright'] ) ? 130 : 180;
653 }
654
655 $thumb = false;
656 $noscale = false;
657 $manualthumb = false;
658 $manual_title = '';
659 $rdfaType = 'mw:File/Thumb';
660
661 if ( !$exists ) {
662 // Same precedence as the $exists case
663 if ( !isset( $frameParams['manualthumb'] ) && isset( $frameParams['framed'] ) ) {
664 $rdfaType = 'mw:File/Frame';
665 }
666 $outerWidth = $handlerParams['width'] + 2;
667 } else {
668 if ( isset( $frameParams['manualthumb'] ) ) {
669 # Use manually specified thumbnail
670 $manual_title = Title::makeTitleSafe( NS_FILE, $frameParams['manualthumb'] );
671 if ( $manual_title ) {
672 $manual_img = $services->getRepoGroup()
673 ->findFile( $manual_title );
674 if ( $manual_img ) {
675 $thumb = $manual_img->getUnscaledThumb( $handlerParams );
676 $manualthumb = true;
677 }
678 }
679 } elseif ( isset( $frameParams['framed'] ) ) {
680 // Use image dimensions, don't scale
681 $thumb = $file->getUnscaledThumb( $handlerParams );
682 $noscale = true;
683 $rdfaType = 'mw:File/Frame';
684 } else {
685 # Do not present an image bigger than the source, for bitmap-style images
686 # This is a hack to maintain compatibility with arbitrary pre-1.10 behavior
687 $srcWidth = $file->getWidth( $page );
688 if ( $srcWidth && !$file->mustRender() && $handlerParams['width'] > $srcWidth ) {
689 $handlerParams['width'] = $srcWidth;
690 }
691 $thumb = $file->transform( $handlerParams );
692 }
693
694 if ( $thumb ) {
695 $outerWidth = $thumb->getWidth() + 2;
696 } else {
697 $outerWidth = $handlerParams['width'] + 2;
698 }
699 }
700
701 $url = Title::newFromLinkTarget( $title )->getLocalURL( $query );
702 $linkTitleQuery = [];
703
704 if ( $page ) {
705 $linkTitleQuery['page'] = $page;
706 # ThumbnailImage::toHtml() already adds page= onto the end of DjVu URLs
707 # So we don't need to pass it here in $query. However, the URL for the
708 # zoom icon still needs it, so we make a unique query for it. See T16771
709 # FIXME: What about "lang" and other querystring parameters
710 $url = wfAppendQuery( $url, $linkTitleQuery );
711 }
712
713 if ( $manualthumb
714 && !isset( $frameParams['link-title'] )
715 && !isset( $frameParams['link-url'] )
716 && !isset( $frameParams['no-link'] ) ) {
717 $frameParams['link-title'] = $title;
718 $frameParams['link-title-query'] = $linkTitleQuery;
719 }
720
721 if ( $frameParams['align'] != '' ) {
722 // Possible values: mw-halign-left mw-halign-center mw-halign-right mw-halign-none
723 $classes[] = "mw-halign-{$frameParams['align']}";
724 }
725
726 if ( isset( $frameParams['class'] ) ) {
727 $classes[] = $frameParams['class'];
728 }
729
730 $s = '';
731
732 if ( $enableLegacyMediaDOM ) {
733 $s .= "<div class=\"thumb t{$frameParams['align']}\">"
734 . "<div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
735 }
736
737 $isBadFile = $exists && $thumb && $parser &&
738 $parser->getBadFileLookup()->isBadFile(
739 $manualthumb ? $manual_title : $title->getDBkey(),
740 $parser->getTitle()
741 );
742
743 if ( !$exists ) {
744 $rdfaType = 'mw:Error ' . $rdfaType;
745 $label = '';
747 $title, $label, '', '', '', (bool)$time, $handlerParams, false
748 );
749 $zoomIcon = '';
750 } elseif ( !$thumb || ( !$enableLegacyMediaDOM && $thumb->isError() ) || $isBadFile ) {
751 $rdfaType = 'mw:Error ' . $rdfaType;
752 if ( $enableLegacyMediaDOM ) {
753 if ( !$thumb ) {
754 $s .= wfMessage( 'thumbnail_error', '' )->escaped();
755 } else {
757 $title, '', '', '', '', (bool)$time, $handlerParams, true
758 );
759 }
760 } else {
761 if ( $thumb && $thumb->isError() ) {
762 Assert::invariant(
763 $thumb instanceof MediaTransformError,
764 'Unknown MediaTransformOutput: ' . get_class( $thumb )
765 );
766 $label = $thumb->toText();
767 } elseif ( !$thumb ) {
768 $label = wfMessage( 'thumbnail_error', '' )->text();
769 } else {
770 $label = '';
771 }
773 $title, $label, '', '', '', (bool)$time, $handlerParams, true
774 );
775 }
776 $zoomIcon = '';
777 } else {
778 if ( !$noscale && !$manualthumb ) {
779 self::processResponsiveImages( $file, $thumb, $handlerParams );
780 }
781 $params = [
782 'alt' => $frameParams['alt'],
783 ];
784 if ( $enableLegacyMediaDOM ) {
785 $params += [
786 'img-class' => ( isset( $frameParams['class'] ) && $frameParams['class'] !== ''
787 ? $frameParams['class'] . ' '
788 : '' ) . 'thumbimage'
789 ];
790 }
791 $params = self::getImageLinkMTOParams( $frameParams, $query, $parser ) + $params;
792 $s .= $thumb->toHtml( $params );
793 if ( isset( $frameParams['framed'] ) ) {
794 $zoomIcon = '';
795 } else {
796 $zoomIcon = Html::rawElement( 'div', [ 'class' => 'magnify' ],
797 Html::rawElement( 'a', [
798 'href' => $url,
799 'class' => 'internal',
800 'title' => wfMessage( 'thumbnail-more' )->text(),
801 ] )
802 );
803 }
804 }
805
806 if ( $enableLegacyMediaDOM ) {
807 $s .= ' <div class="thumbcaption">' . $zoomIcon . $frameParams['caption'] . '</div></div></div>';
808 return str_replace( "\n", ' ', $s );
809 }
810
811 $s .= Html::rawElement(
812 'figcaption', [], $frameParams['caption'] ?? ''
813 );
814
815 $attribs = [
816 'class' => $classes,
817 'typeof' => $rdfaType,
818 ];
819
820 $s = Html::rawElement( 'figure', $attribs, $s );
821
822 return str_replace( "\n", ' ', $s );
823 }
824
833 public static function processResponsiveImages( $file, $thumb, $hp ) {
834 $responsiveImages = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::ResponsiveImages );
835 if ( $responsiveImages && $thumb && !$thumb->isError() ) {
836 $hp15 = $hp;
837 $hp15['width'] = round( $hp['width'] * 1.5 );
838 $hp20 = $hp;
839 $hp20['width'] = $hp['width'] * 2;
840 if ( isset( $hp['height'] ) ) {
841 $hp15['height'] = round( $hp['height'] * 1.5 );
842 $hp20['height'] = $hp['height'] * 2;
843 }
844
845 $thumb15 = $file->transform( $hp15 );
846 $thumb20 = $file->transform( $hp20 );
847 if ( $thumb15 && !$thumb15->isError() && $thumb15->getUrl() !== $thumb->getUrl() ) {
848 $thumb->responsiveUrls['1.5'] = $thumb15->getUrl();
849 }
850 if ( $thumb20 && !$thumb20->isError() && $thumb20->getUrl() !== $thumb->getUrl() ) {
851 $thumb->responsiveUrls['2'] = $thumb20->getUrl();
852 }
853 }
854 }
855
870 public static function makeBrokenImageLinkObj(
871 $title, $label = '', $query = '', $unused1 = '', $unused2 = '',
872 $time = false, array $handlerParams = [], bool $currentExists = false
873 ) {
874 if ( !$title instanceof LinkTarget ) {
875 wfWarn( __METHOD__ . ': Requires $title to be a LinkTarget object.' );
876 return "<!-- ERROR -->" . htmlspecialchars( $label );
877 }
878
879 $title = Title::castFromLinkTarget( $title );
880 $services = MediaWikiServices::getInstance();
881 $mainConfig = $services->getMainConfig();
882 $enableUploads = $mainConfig->get( MainConfigNames::EnableUploads );
883 $uploadMissingFileUrl = $mainConfig->get( MainConfigNames::UploadMissingFileUrl );
884 $uploadNavigationUrl = $mainConfig->get( MainConfigNames::UploadNavigationUrl );
885 if ( $label == '' ) {
886 $label = $title->getPrefixedText();
887 }
888
889 $html = Html::element( 'span', [
890 'class' => 'mw-broken-media',
891 // These data attributes are used to dynamically size the span, see T273013
892 'data-width' => $handlerParams['width'] ?? null,
893 'data-height' => $handlerParams['height'] ?? null,
894 ], $label );
895
896 if ( $mainConfig->get( MainConfigNames::ParserEnableLegacyMediaDOM ) ) {
897 $html = htmlspecialchars( $label, ENT_COMPAT );
898 }
899
900 $repoGroup = $services->getRepoGroup();
901 $currentExists = $currentExists ||
902 ( $time && $repoGroup->findFile( $title ) !== false );
903
904 if ( ( $uploadMissingFileUrl || $uploadNavigationUrl || $enableUploads )
905 && !$currentExists
906 ) {
907 if (
908 $title->inNamespace( NS_FILE ) &&
909 $repoGroup->getLocalRepo()->checkRedirect( $title )
910 ) {
911 // We already know it's a redirect, so mark it accordingly
912 return self::link(
913 $title,
914 $html,
915 [ 'class' => 'mw-redirect' ],
916 wfCgiToArray( $query ),
917 [ 'known', 'noclasses' ]
918 );
919 }
920 return Html::rawElement( 'a', [
921 'href' => self::getUploadUrl( $title, $query ),
922 'class' => 'new',
923 'title' => $title->getPrefixedText()
924 ], $html );
925 }
926 return self::link(
927 $title,
928 $html,
929 [],
930 wfCgiToArray( $query ),
931 [ 'known', 'noclasses' ]
932 );
933 }
934
943 protected static function getUploadUrl( $destFile, $query = '' ) {
944 $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
945 $uploadMissingFileUrl = $mainConfig->get( MainConfigNames::UploadMissingFileUrl );
946 $uploadNavigationUrl = $mainConfig->get( MainConfigNames::UploadNavigationUrl );
947 $q = 'wpDestFile=' . Title::castFromLinkTarget( $destFile )->getPartialURL();
948 if ( $query != '' ) {
949 $q .= '&' . $query;
950 }
951
952 if ( $uploadMissingFileUrl ) {
953 return wfAppendQuery( $uploadMissingFileUrl, $q );
954 }
955
956 if ( $uploadNavigationUrl ) {
957 return wfAppendQuery( $uploadNavigationUrl, $q );
958 }
959
960 $upload = SpecialPage::getTitleFor( 'Upload' );
961
962 return $upload->getLocalURL( $q );
963 }
964
974 public static function makeMediaLinkObj( $title, $html = '', $time = false ) {
975 $img = MediaWikiServices::getInstance()->getRepoGroup()->findFile(
976 $title, [ 'time' => $time ]
977 );
978 return self::makeMediaLinkFile( $title, $img, $html );
979 }
980
993 public static function makeMediaLinkFile( LinkTarget $title, $file, $html = '' ) {
994 if ( $file && $file->exists() ) {
995 $url = $file->getUrl();
996 $class = 'internal';
997 } else {
998 $url = self::getUploadUrl( $title );
999 $class = 'new';
1000 }
1001
1002 $alt = $title->getText();
1003 if ( $html == '' ) {
1004 $html = $alt;
1005 }
1006
1007 $ret = '';
1008 $attribs = [
1009 'href' => $url,
1010 'class' => $class,
1011 'title' => $alt
1012 ];
1013
1014 if ( !Hooks::runner()->onLinkerMakeMediaLinkFile(
1015 Title::castFromLinkTarget( $title ), $file, $html, $attribs, $ret )
1016 ) {
1017 wfDebug( "Hook LinkerMakeMediaLinkFile changed the output of link "
1018 . "with url {$url} and text {$html} to {$ret}" );
1019 return $ret;
1020 }
1021
1022 return Html::rawElement( 'a', $attribs, $html );
1023 }
1024
1035 public static function specialLink( $name, $key = '' ) {
1036 if ( $key == '' ) {
1037 $key = strtolower( $name );
1038 }
1039
1040 return self::linkKnown( SpecialPage::getTitleFor( $name ), wfMessage( $key )->escaped() );
1041 }
1042
1061 public static function makeExternalLink( $url, $text, $escape = true,
1062 $linktype = '', $attribs = [], $title = null
1063 ) {
1064 global $wgTitle;
1065 $class = 'external';
1066 if ( $linktype ) {
1067 $class .= " $linktype";
1068 }
1069 if ( isset( $attribs['class'] ) && $attribs['class'] ) {
1070 $class .= " {$attribs['class']}";
1071 }
1072 $attribs['class'] = $class;
1073
1074 if ( $escape ) {
1075 $text = htmlspecialchars( $text, ENT_COMPAT );
1076 }
1077
1078 if ( !$title ) {
1079 $title = $wgTitle;
1080 }
1081 $newRel = Parser::getExternalLinkRel( $url, $title );
1082 if ( !isset( $attribs['rel'] ) || $attribs['rel'] === '' ) {
1083 $attribs['rel'] = $newRel;
1084 } elseif ( $newRel !== null ) {
1085 // Merge the rel attributes.
1086 $newRels = explode( ' ', $newRel );
1087 $oldRels = explode( ' ', $attribs['rel'] );
1088 $combined = array_unique( array_merge( $newRels, $oldRels ) );
1089 $attribs['rel'] = implode( ' ', $combined );
1090 }
1091 $link = '';
1092 $success = Hooks::runner()->onLinkerMakeExternalLink(
1093 $url, $text, $link, $attribs, $linktype );
1094 if ( !$success ) {
1095 wfDebug( "Hook LinkerMakeExternalLink changed the output of link "
1096 . "with url {$url} and text {$text} to {$link}" );
1097 return $link;
1098 }
1099 $attribs['href'] = $url;
1100 return Html::rawElement( 'a', $attribs, $text );
1101 }
1102
1114 public static function userLink( $userId, $userName, $altUserName = false ) {
1115 if ( $userName === '' || $userName === false || $userName === null ) {
1116 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1117 'that need to be fixed?' );
1118 return wfMessage( 'empty-username' )->parse();
1119 }
1120
1121 $classes = 'mw-userlink';
1122 $page = null;
1123 if ( $userId == 0 ) {
1124 $page = ExternalUserNames::getUserLinkTitle( $userName );
1125
1126 if ( ExternalUserNames::isExternal( $userName ) ) {
1127 $classes .= ' mw-extuserlink';
1128 } elseif ( $altUserName === false ) {
1129 $altUserName = IPUtils::prettifyIP( $userName );
1130 }
1131 $classes .= ' mw-anonuserlink'; // Separate link class for anons (T45179)
1132 } else {
1133 $page = TitleValue::tryNew( NS_USER, strtr( $userName, ' ', '_' ) );
1134 }
1135
1136 // Wrap the output with <bdi> tags for directionality isolation
1137 $linkText =
1138 '<bdi>' . htmlspecialchars( $altUserName !== false ? $altUserName : $userName ) . '</bdi>';
1139
1140 return $page
1141 ? self::link( $page, $linkText, [ 'class' => $classes ] )
1142 : Html::rawElement( 'span', [ 'class' => $classes ], $linkText );
1143 }
1144
1159 public static function userToolLinks(
1160 $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits = null,
1161 $useParentheses = true
1162 ) {
1163 if ( $userText === '' ) {
1164 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1165 'that need to be fixed?' );
1166 return ' ' . wfMessage( 'empty-username' )->parse();
1167 }
1168 global $wgLang;
1169 $disableAnonTalk = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::DisableAnonTalk );
1170 $talkable = !( $disableAnonTalk && $userId == 0 );
1171 $blockable = !( $flags & self::TOOL_LINKS_NOBLOCK );
1172 $addEmailLink = $flags & self::TOOL_LINKS_EMAIL && $userId;
1173
1174 if ( $userId == 0 && ExternalUserNames::isExternal( $userText ) ) {
1175 // No tools for an external user
1176 return '';
1177 }
1178
1179 $items = [];
1180 if ( $talkable ) {
1181 $items[] = self::userTalkLink( $userId, $userText );
1182 }
1183 if ( $userId ) {
1184 // check if the user has an edit
1185 $attribs = [];
1186 $attribs['class'] = 'mw-usertoollinks-contribs';
1187 if ( $redContribsWhenNoEdits ) {
1188 if ( intval( $edits ) === 0 && $edits !== 0 ) {
1189 $user = User::newFromId( $userId );
1190 $edits = $user->getEditCount();
1191 }
1192 if ( $edits === 0 ) {
1193 // Note: "new" class is inappropriate here, as "new" class
1194 // should only be used for pages that do not exist.
1195 $attribs['class'] .= ' mw-usertoollinks-contribs-no-edits';
1196 }
1197 }
1198 $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText );
1199
1200 $items[] = self::link( $contribsPage, wfMessage( 'contribslink' )->escaped(), $attribs );
1201 }
1202 $userCanBlock = RequestContext::getMain()->getAuthority()->isAllowed( 'block' );
1203 if ( $blockable && $userCanBlock ) {
1204 $items[] = self::blockLink( $userId, $userText );
1205 }
1206
1207 $user = RequestContext::getMain()->getUser();
1208 if ( $addEmailLink && $user->canSendEmail() ) {
1209 $items[] = self::emailLink( $userId, $userText );
1210 }
1211
1212 Hooks::runner()->onUserToolLinksEdit( $userId, $userText, $items );
1213
1214 if ( !$items ) {
1215 return '';
1216 }
1217
1218 if ( $useParentheses ) {
1219 return wfMessage( 'word-separator' )->escaped()
1220 . '<span class="mw-usertoollinks">'
1221 . wfMessage( 'parentheses' )->rawParams( $wgLang->pipeList( $items ) )->escaped()
1222 . '</span>';
1223 }
1224
1225 $tools = [];
1226 foreach ( $items as $tool ) {
1227 $tools[] = Html::rawElement( 'span', [], $tool );
1228 }
1229 return ' <span class="mw-usertoollinks mw-changeslist-links">' .
1230 implode( ' ', $tools ) . '</span>';
1231 }
1232
1242 public static function userToolLinksRedContribs(
1243 $userId, $userText, $edits = null, $useParentheses = true
1244 ) {
1245 return self::userToolLinks( $userId, $userText, true, 0, $edits, $useParentheses );
1246 }
1247
1254 public static function userTalkLink( $userId, $userText ) {
1255 if ( $userText === '' ) {
1256 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1257 'that need to be fixed?' );
1258 return wfMessage( 'empty-username' )->parse();
1259 }
1260
1261 $userTalkPage = TitleValue::tryNew( NS_USER_TALK, strtr( $userText, ' ', '_' ) );
1262 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-talk' ];
1263 $linkText = wfMessage( 'talkpagelinktext' )->escaped();
1264
1265 return $userTalkPage
1266 ? self::link( $userTalkPage, $linkText, $moreLinkAttribs )
1267 : Html::rawElement( 'span', $moreLinkAttribs, $linkText );
1268 }
1269
1276 public static function blockLink( $userId, $userText ) {
1277 if ( $userText === '' ) {
1278 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1279 'that need to be fixed?' );
1280 return wfMessage( 'empty-username' )->parse();
1281 }
1282
1283 $blockPage = SpecialPage::getTitleFor( 'Block', $userText );
1284 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-block' ];
1285
1286 return self::link( $blockPage,
1287 wfMessage( 'blocklink' )->escaped(),
1288 $moreLinkAttribs
1289 );
1290 }
1291
1297 public static function emailLink( $userId, $userText ) {
1298 if ( $userText === '' ) {
1299 wfLogWarning( __METHOD__ . ' received an empty username. Are there database errors ' .
1300 'that need to be fixed?' );
1301 return wfMessage( 'empty-username' )->parse();
1302 }
1303
1304 $emailPage = SpecialPage::getTitleFor( 'Emailuser', $userText );
1305 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-mail' ];
1306 return self::link( $emailPage,
1307 wfMessage( 'emaillink' )->escaped(),
1308 $moreLinkAttribs
1309 );
1310 }
1311
1323 public static function revUserLink( RevisionRecord $revRecord, $isPublic = false ) {
1324 // TODO inject authority
1325 $authority = RequestContext::getMain()->getAuthority();
1326
1327 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) && $isPublic ) {
1328 $link = wfMessage( 'rev-deleted-user' )->escaped();
1329 } elseif ( $revRecord->userCan( RevisionRecord::DELETED_USER, $authority ) ) {
1330 $revUser = $revRecord->getUser( RevisionRecord::FOR_THIS_USER, $authority );
1331 $link = self::userLink(
1332 $revUser ? $revUser->getId() : 0,
1333 $revUser ? $revUser->getName() : ''
1334 );
1335 } else {
1336 $link = wfMessage( 'rev-deleted-user' )->escaped();
1337 }
1338 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
1339 $class = self::getRevisionDeletedClass( $revRecord );
1340 return '<span class="' . $class . '">' . $link . '</span>';
1341 }
1342 return $link;
1343 }
1344
1351 public static function getRevisionDeletedClass( RevisionRecord $revisionRecord ): string {
1352 $class = 'history-deleted';
1353 if ( $revisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
1354 $class .= ' mw-history-suppressed';
1355 }
1356 return $class;
1357 }
1358
1371 public static function revUserTools(
1372 RevisionRecord $revRecord,
1373 $isPublic = false,
1374 $useParentheses = true
1375 ) {
1376 // TODO inject authority
1377 $authority = RequestContext::getMain()->getAuthority();
1378
1379 if ( $revRecord->userCan( RevisionRecord::DELETED_USER, $authority ) &&
1380 ( !$revRecord->isDeleted( RevisionRecord::DELETED_USER ) || !$isPublic )
1381 ) {
1382 $revUser = $revRecord->getUser( RevisionRecord::FOR_THIS_USER, $authority );
1383 $userId = $revUser ? $revUser->getId() : 0;
1384 $userText = $revUser ? $revUser->getName() : '';
1385
1386 if ( $userId || $userText !== '' ) {
1387 $link = self::userLink( $userId, $userText )
1388 . self::userToolLinks( $userId, $userText, false, 0, null,
1389 $useParentheses );
1390 }
1391 }
1392
1393 if ( !isset( $link ) ) {
1394 $link = wfMessage( 'rev-deleted-user' )->escaped();
1395 }
1396
1397 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
1398 $class = self::getRevisionDeletedClass( $revRecord );
1399 return ' <span class="' . $class . ' mw-userlink">' . $link . '</span>';
1400 }
1401 return $link;
1402 }
1403
1414 public static function expandLocalLinks( string $html ) {
1415 $formatter = new HtmlFormatter( $html );
1416 $doc = $formatter->getDoc();
1417 $xpath = new DOMXPath( $doc );
1418 $nodes = $xpath->query( '//a[@href]' );
1420 foreach ( $nodes as $node ) {
1421 $node->setAttribute(
1422 'href',
1423 wfExpandUrl( $node->getAttribute( 'href' ), PROTO_RELATIVE )
1424 );
1425 }
1426 return $formatter->getText( 'html' );
1427 }
1428
1449 public static function formatComment(
1450 $comment, $title = null, $local = false, $wikiId = null
1451 ) {
1452 $formatter = MediaWikiServices::getInstance()->getCommentFormatter();
1453 return $formatter->format( $comment, $title, $local, $wikiId );
1454 }
1455
1475 public static function formatLinksInComment(
1476 $comment, $title = null, $local = false, $wikiId = null
1477 ) {
1478 $formatter = MediaWikiServices::getInstance()->getCommentFormatter();
1479 return $formatter->formatLinksUnsafe( $comment, $title, $local, $wikiId );
1480 }
1481
1488 public static function normalizeSubpageLink( $contextTitle, $target, &$text ) {
1489 # Valid link forms:
1490 # Foobar -- normal
1491 # :Foobar -- override special treatment of prefix (images, language links)
1492 # /Foobar -- convert to CurrentPage/Foobar
1493 # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial and final / from text
1494 # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage
1495 # ../Foobar -- convert to CurrentPage/Foobar,
1496 # (from CurrentPage/CurrentSubPage)
1497 # ../Foobar/ -- convert to CurrentPage/Foobar, use 'Foobar' as text
1498 # (from CurrentPage/CurrentSubPage)
1499
1500 $ret = $target; # default return value is no change
1501
1502 # Some namespaces don't allow subpages,
1503 # so only perform processing if subpages are allowed
1504 if (
1505 $contextTitle && MediaWikiServices::getInstance()->getNamespaceInfo()->
1506 hasSubpages( $contextTitle->getNamespace() )
1507 ) {
1508 $hash = strpos( $target, '#' );
1509 if ( $hash !== false ) {
1510 $suffix = substr( $target, $hash );
1511 $target = substr( $target, 0, $hash );
1512 } else {
1513 $suffix = '';
1514 }
1515 # T9425
1516 $target = trim( $target );
1517 $contextPrefixedText = MediaWikiServices::getInstance()->getTitleFormatter()->
1518 getPrefixedText( $contextTitle );
1519 # Look at the first character
1520 if ( $target != '' && $target[0] === '/' ) {
1521 # / at end means we don't want the slash to be shown
1522 $m = [];
1523 $trailingSlashes = preg_match_all( '%(/+)$%', $target, $m );
1524 if ( $trailingSlashes ) {
1525 $noslash = $target = substr( $target, 1, -strlen( $m[0][0] ) );
1526 } else {
1527 $noslash = substr( $target, 1 );
1528 }
1529
1530 $ret = $contextPrefixedText . '/' . trim( $noslash ) . $suffix;
1531 if ( $text === '' ) {
1532 $text = $target . $suffix;
1533 } # this might be changed for ugliness reasons
1534 } else {
1535 # check for .. subpage backlinks
1536 $dotdotcount = 0;
1537 $nodotdot = $target;
1538 while ( str_starts_with( $nodotdot, '../' ) ) {
1539 ++$dotdotcount;
1540 $nodotdot = substr( $nodotdot, 3 );
1541 }
1542 if ( $dotdotcount > 0 ) {
1543 $exploded = explode( '/', $contextPrefixedText );
1544 if ( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page
1545 $ret = implode( '/', array_slice( $exploded, 0, -$dotdotcount ) );
1546 # / at the end means don't show full path
1547 if ( substr( $nodotdot, -1, 1 ) === '/' ) {
1548 $nodotdot = rtrim( $nodotdot, '/' );
1549 if ( $text === '' ) {
1550 $text = $nodotdot . $suffix;
1551 }
1552 }
1553 $nodotdot = trim( $nodotdot );
1554 if ( $nodotdot != '' ) {
1555 $ret .= '/' . $nodotdot;
1556 }
1557 $ret .= $suffix;
1558 }
1559 }
1560 }
1561 }
1562
1563 return $ret;
1564 }
1565
1585 public static function commentBlock(
1586 $comment, $title = null, $local = false, $wikiId = null, $useParentheses = true
1587 ) {
1588 return MediaWikiServices::getInstance()->getCommentFormatter()
1589 ->formatBlock( $comment, $title, $local, $wikiId, $useParentheses );
1590 }
1591
1607 public static function revComment(
1608 RevisionRecord $revRecord,
1609 $local = false,
1610 $isPublic = false,
1611 $useParentheses = true
1612 ) {
1613 $authority = RequestContext::getMain()->getAuthority();
1614 $formatter = MediaWikiServices::getInstance()->getCommentFormatter();
1615 return $formatter->formatRevision( $revRecord, $authority, $local, $isPublic, $useParentheses );
1616 }
1617
1623 public static function formatRevisionSize( $size ) {
1624 if ( $size == 0 ) {
1625 $stxt = wfMessage( 'historyempty' )->escaped();
1626 } else {
1627 $stxt = wfMessage( 'nbytes' )->numParams( $size )->escaped();
1628 }
1629 return "<span class=\"history-size mw-diff-bytes\" data-mw-bytes=\"$size\">$stxt</span>";
1630 }
1631
1638 public static function tocIndent() {
1639 return "\n<ul>\n";
1640 }
1641
1649 public static function tocUnindent( $level ) {
1650 return "</li>\n" . str_repeat( "</ul>\n</li>\n", $level > 0 ? $level : 0 );
1651 }
1652
1664 public static function tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex = false ) {
1665 $classes = "toclevel-$level";
1666 if ( $sectionIndex !== false ) {
1667 $classes .= " tocsection-$sectionIndex";
1668 }
1669
1670 // <li class="$classes"><a href="#$anchor"><span class="tocnumber">
1671 // $tocnumber</span> <span class="toctext">$tocline</span></a>
1672 return Html::openElement( 'li', [ 'class' => $classes ] )
1673 . Html::rawElement( 'a',
1674 [ 'href' => "#$anchor" ],
1675 Html::element( 'span', [ 'class' => 'tocnumber' ], $tocnumber )
1676 . ' '
1677 . Html::rawElement( 'span', [ 'class' => 'toctext' ], $tocline )
1678 );
1679 }
1680
1688 public static function tocLineEnd() {
1689 return "</li>\n";
1690 }
1691
1700 public static function tocList( $toc, Language $lang = null ) {
1701 $lang = $lang ?? RequestContext::getMain()->getLanguage();
1702
1703 $title = wfMessage( 'toc' )->inLanguage( $lang )->escaped();
1704
1705 return '<div id="toc" class="toc" role="navigation" aria-labelledby="mw-toc-heading">'
1706 . Html::element( 'input', [
1707 'type' => 'checkbox',
1708 'role' => 'button',
1709 'id' => 'toctogglecheckbox',
1710 'class' => 'toctogglecheckbox',
1711 'style' => 'display:none',
1712 ] )
1713 . Html::openElement( 'div', [
1714 'class' => 'toctitle',
1715 'lang' => $lang->getHtmlCode(),
1716 'dir' => $lang->getDir(),
1717 ] )
1718 . '<h2 id="mw-toc-heading">' . $title . '</h2>'
1719 . '<span class="toctogglespan">'
1720 . Html::label( '', 'toctogglecheckbox', [
1721 'class' => 'toctogglelabel',
1722 ] )
1723 . '</span>'
1724 . '</div>'
1725 . $toc
1726 . "</ul>\n</div>\n";
1727 }
1728
1737 public static function generateTOC( $tree, Language $lang = null ) {
1738 $toc = '';
1739 $lastLevel = 0;
1740 foreach ( $tree as $section ) {
1741 if ( $section['toclevel'] > $lastLevel ) {
1742 $toc .= self::tocIndent();
1743 } elseif ( $section['toclevel'] < $lastLevel ) {
1744 $toc .= self::tocUnindent(
1745 $lastLevel - $section['toclevel'] );
1746 } else {
1747 $toc .= self::tocLineEnd();
1748 }
1749
1750 $toc .= self::tocLine( $section['anchor'],
1751 $section['line'], $section['number'],
1752 $section['toclevel'], $section['index'] );
1753 $lastLevel = $section['toclevel'];
1754 }
1755 $toc .= self::tocLineEnd();
1756 return self::tocList( $toc, $lang );
1757 }
1758
1775 public static function makeHeadline( $level, $attribs, $anchor, $html,
1776 $link, $fallbackAnchor = false
1777 ) {
1778 $anchorEscaped = htmlspecialchars( $anchor, ENT_COMPAT );
1779 $fallback = '';
1780 if ( $fallbackAnchor !== false && $fallbackAnchor !== $anchor ) {
1781 $fallbackAnchor = htmlspecialchars( $fallbackAnchor, ENT_COMPAT );
1782 $fallback = "<span id=\"$fallbackAnchor\"></span>";
1783 }
1784 return "<h$level$attribs"
1785 . "$fallback<span class=\"mw-headline\" id=\"$anchorEscaped\">$html</span>"
1786 . $link
1787 . "</h$level>";
1788 }
1789
1796 public static function splitTrail( $trail ) {
1797 $regex = MediaWikiServices::getInstance()->getContentLanguage()->linkTrail();
1798 $inside = '';
1799 if ( $trail !== '' && preg_match( $regex, $trail, $m ) ) {
1800 [ , $inside, $trail ] = $m;
1801 }
1802 return [ $inside, $trail ];
1803 }
1804
1834 public static function generateRollback(
1835 RevisionRecord $revRecord,
1836 IContextSource $context = null,
1837 $options = [ 'verify' ]
1838 ) {
1839 if ( $context === null ) {
1840 $context = RequestContext::getMain();
1841 }
1842
1843 $editCount = false;
1844 if ( in_array( 'verify', $options, true ) ) {
1845 $editCount = self::getRollbackEditCount( $revRecord, true );
1846 if ( $editCount === false ) {
1847 return '';
1848 }
1849 }
1850
1851 $inner = self::buildRollbackLink( $revRecord, $context, $editCount );
1852
1853 // Allow extensions to modify the rollback link.
1854 // Abort further execution if the extension wants full control over the link.
1855 if ( !Hooks::runner()->onLinkerGenerateRollbackLink(
1856 $revRecord, $context, $options, $inner ) ) {
1857 return $inner;
1858 }
1859
1860 if ( !in_array( 'noBrackets', $options, true ) ) {
1861 $inner = $context->msg( 'brackets' )->rawParams( $inner )->escaped();
1862 }
1863
1864 if ( MediaWikiServices::getInstance()->getUserOptionsLookup()
1865 ->getBoolOption( $context->getUser(), 'showrollbackconfirmation' )
1866 ) {
1867 $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
1868 $stats->increment( 'rollbackconfirmation.event.load' );
1869 $context->getOutput()->addModules( 'mediawiki.misc-authed-curate' );
1870 }
1871
1872 return '<span class="mw-rollback-link">' . $inner . '</span>';
1873 }
1874
1893 public static function getRollbackEditCount( RevisionRecord $revRecord, $verify ) {
1894 $showRollbackEditCount = MediaWikiServices::getInstance()->getMainConfig()
1895 ->get( MainConfigNames::ShowRollbackEditCount );
1896
1897 if ( !is_int( $showRollbackEditCount ) || !$showRollbackEditCount > 0 ) {
1898 // Nothing has happened, indicate this by returning 'null'
1899 return null;
1900 }
1901
1902 $dbr = wfGetDB( DB_REPLICA );
1903
1904 // Up to the value of $wgShowRollbackEditCount revisions are counted
1905 $revQuery = MediaWikiServices::getInstance()->getRevisionStore()->getQueryInfo();
1906 $res = $dbr->newSelectQueryBuilder()
1907 ->select( [ 'rev_user_text' => $revQuery['fields']['rev_user_text'], 'rev_deleted' ] )
1908 ->tables( $revQuery['tables'] )
1909 ->where( [ 'rev_page' => $revRecord->getPageId() ] )
1910 ->joinConds( $revQuery['joins'] )
1911 ->useIndex( [ 'revision' => 'rev_page_timestamp' ] )
1912 ->orderBy( [ 'rev_timestamp', 'rev_id' ], SelectQueryBuilder::SORT_DESC )
1913 ->limit( $showRollbackEditCount + 1 )
1914 ->caller( __METHOD__ )
1915 ->fetchResultSet();
1916
1917 $revUser = $revRecord->getUser( RevisionRecord::RAW );
1918 $revUserText = $revUser ? $revUser->getName() : '';
1919
1920 $editCount = 0;
1921 $moreRevs = false;
1922 foreach ( $res as $row ) {
1923 if ( $row->rev_user_text != $revUserText ) {
1924 if ( $verify &&
1925 ( $row->rev_deleted & RevisionRecord::DELETED_TEXT
1926 || $row->rev_deleted & RevisionRecord::DELETED_USER
1927 ) ) {
1928 // If the user or the text of the revision we might rollback
1929 // to is deleted in some way we can't rollback. Similar to
1930 // the checks in WikiPage::commitRollback.
1931 return false;
1932 }
1933 $moreRevs = true;
1934 break;
1935 }
1936 $editCount++;
1937 }
1938
1939 if ( $verify && $editCount <= $showRollbackEditCount && !$moreRevs ) {
1940 // We didn't find at least $wgShowRollbackEditCount revisions made by the current user
1941 // and there weren't any other revisions. That means that the current user is the only
1942 // editor, so we can't rollback
1943 return false;
1944 }
1945 return $editCount;
1946 }
1947
1962 public static function buildRollbackLink(
1963 RevisionRecord $revRecord,
1964 IContextSource $context = null,
1965 $editCount = false
1966 ) {
1967 $config = MediaWikiServices::getInstance()->getMainConfig();
1968 $showRollbackEditCount = $config->get( MainConfigNames::ShowRollbackEditCount );
1969 $miserMode = $config->get( MainConfigNames::MiserMode );
1970 // To config which pages are affected by miser mode
1971 $disableRollbackEditCountSpecialPage = [ 'Recentchanges', 'Watchlist' ];
1972
1973 if ( $context === null ) {
1974 $context = RequestContext::getMain();
1975 }
1976
1977 $title = $revRecord->getPageAsLinkTarget();
1978 $revUser = $revRecord->getUser();
1979 $revUserText = $revUser ? $revUser->getName() : '';
1980
1981 $query = [
1982 'action' => 'rollback',
1983 'from' => $revUserText,
1984 'token' => $context->getUser()->getEditToken( 'rollback' ),
1985 ];
1986
1987 $attrs = [
1988 'data-mw' => 'interface',
1989 'title' => $context->msg( 'tooltip-rollback' )->text()
1990 ];
1991
1992 $options = [ 'known', 'noclasses' ];
1993
1994 if ( $context->getRequest()->getBool( 'bot' ) ) {
1995 // T17999
1996 $query['hidediff'] = '1';
1997 $query['bot'] = '1';
1998 }
1999
2000 if ( $miserMode ) {
2001 foreach ( $disableRollbackEditCountSpecialPage as $specialPage ) {
2002 if ( $context->getTitle()->isSpecial( $specialPage ) ) {
2003 $showRollbackEditCount = false;
2004 break;
2005 }
2006 }
2007 }
2008
2009 // The edit count can be 0 on replica lag, fall back to the generic rollbacklink message
2010 $msg = [ 'rollbacklink' ];
2011 if ( is_int( $showRollbackEditCount ) && $showRollbackEditCount > 0 ) {
2012 if ( !is_numeric( $editCount ) ) {
2013 $editCount = self::getRollbackEditCount( $revRecord, false );
2014 }
2015
2016 if ( $editCount > $showRollbackEditCount ) {
2017 $msg = [ 'rollbacklinkcount-morethan', Message::numParam( $showRollbackEditCount ) ];
2018 } elseif ( $editCount ) {
2019 $msg = [ 'rollbacklinkcount', Message::numParam( $editCount ) ];
2020 }
2021 }
2022
2023 $html = $context->msg( ...$msg )->parse();
2024 return self::link( $title, $html, $attrs, $query, $options );
2025 }
2026
2035 public static function formatHiddenCategories( $hiddencats ) {
2036 $outText = '';
2037 if ( count( $hiddencats ) > 0 ) {
2038 # Construct the HTML
2039 $outText = '<div class="mw-hiddenCategoriesExplanation">';
2040 $outText .= wfMessage( 'hiddencategories' )->numParams( count( $hiddencats ) )->parseAsBlock();
2041 $outText .= "</div><ul>\n";
2042
2043 foreach ( $hiddencats as $titleObj ) {
2044 # If it's hidden, it must exist - no need to check with a LinkBatch
2045 $outText .= '<li>'
2046 . self::link( $titleObj, null, [], [], 'known' )
2047 . "</li>\n";
2048 }
2049 $outText .= '</ul>';
2050 }
2051 return $outText;
2052 }
2053
2057 private static function getContextFromMain() {
2058 $context = RequestContext::getMain();
2059 $context = new DerivativeContext( $context );
2060 return $context;
2061 }
2062
2080 public static function titleAttrib( $name, $options = null, array $msgParams = [], $localizer = null ) {
2081 if ( !$localizer ) {
2082 $localizer = self::getContextFromMain();
2083 }
2084 $message = $localizer->msg( "tooltip-$name", $msgParams );
2085 if ( $message->isDisabled() ) {
2086 $tooltip = false;
2087 } else {
2088 $tooltip = $message->text();
2089 # Compatibility: formerly some tooltips had [alt-.] hardcoded
2090 $tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip );
2091 }
2092
2093 $options = (array)$options;
2094
2095 if ( in_array( 'nonexisting', $options ) ) {
2096 $tooltip = $localizer->msg( 'red-link-title', $tooltip ?: '' )->text();
2097 }
2098 if ( in_array( 'withaccess', $options ) ) {
2099 $accesskey = self::accesskey( $name, $localizer );
2100 if ( $accesskey !== false ) {
2101 // Should be build the same as in jquery.accessKeyLabel.js
2102 if ( $tooltip === false || $tooltip === '' ) {
2103 $tooltip = $localizer->msg( 'brackets', $accesskey )->text();
2104 } else {
2105 $tooltip .= $localizer->msg( 'word-separator' )->text();
2106 $tooltip .= $localizer->msg( 'brackets', $accesskey )->text();
2107 }
2108 }
2109 }
2110
2111 return $tooltip;
2112 }
2113
2114 public static $accesskeycache;
2115
2128 public static function accesskey( $name, $localizer = null ) {
2129 if ( !isset( self::$accesskeycache[$name] ) ) {
2130 if ( !$localizer ) {
2131 $localizer = self::getContextFromMain();
2132 }
2133 $msg = $localizer->msg( "accesskey-$name" );
2134 self::$accesskeycache[$name] = $msg->isDisabled() ? false : $msg->plain();
2135 }
2136 return self::$accesskeycache[$name];
2137 }
2138
2153 public static function getRevDeleteLink(
2154 Authority $performer,
2155 RevisionRecord $revRecord,
2157 ) {
2158 $canHide = $performer->isAllowed( 'deleterevision' );
2159 $canHideHistory = $performer->isAllowed( 'deletedhistory' );
2160 if ( !$canHide && !( $revRecord->getVisibility() && $canHideHistory ) ) {
2161 return '';
2162 }
2163
2164 if ( !$revRecord->userCan( RevisionRecord::DELETED_RESTRICTED, $performer ) ) {
2165 return self::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
2166 }
2167 $prefixedDbKey = MediaWikiServices::getInstance()->getTitleFormatter()->
2168 getPrefixedDBkey( $title );
2169 if ( $revRecord->getId() ) {
2170 // RevDelete links using revision ID are stable across
2171 // page deletion and undeletion; use when possible.
2172 $query = [
2173 'type' => 'revision',
2174 'target' => $prefixedDbKey,
2175 'ids' => $revRecord->getId()
2176 ];
2177 } else {
2178 // Older deleted entries didn't save a revision ID.
2179 // We have to refer to these by timestamp, ick!
2180 $query = [
2181 'type' => 'archive',
2182 'target' => $prefixedDbKey,
2183 'ids' => $revRecord->getTimestamp()
2184 ];
2185 }
2186 return self::revDeleteLink(
2187 $query,
2188 $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ),
2189 $canHide
2190 );
2191 }
2192
2205 public static function revDeleteLink( $query = [], $restricted = false, $delete = true ) {
2206 $sp = SpecialPage::getTitleFor( 'Revisiondelete' );
2207 $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
2208 $html = wfMessage( $msgKey )->escaped();
2209 $tag = $restricted ? 'strong' : 'span';
2210 $link = self::link( $sp, $html, [], $query, [ 'known', 'noclasses' ] );
2211 return Xml::tags(
2212 $tag,
2213 [ 'class' => 'mw-revdelundel-link' ],
2214 wfMessage( 'parentheses' )->rawParams( $link )->escaped()
2215 );
2216 }
2217
2229 public static function revDeleteLinkDisabled( $delete = true ) {
2230 $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
2231 $html = wfMessage( $msgKey )->escaped();
2232 $htmlParentheses = wfMessage( 'parentheses' )->rawParams( $html )->escaped();
2233 return Xml::tags( 'span', [ 'class' => 'mw-revdelundel-link' ], $htmlParentheses );
2234 }
2235
2247 private static function updateWatchstarTooltipMessage(
2248 string &$tooltip, array &$msgParams, $config, $user, $relevantTitle
2249 ): void {
2250 if ( !$config || !$user || !$relevantTitle ) {
2251 $mainContext = self::getContextFromMain();
2252 if ( !$config ) {
2253 $config = $mainContext->getConfig();
2254 }
2255 if ( !$user ) {
2256 $user = $mainContext->getUser();
2257 }
2258 if ( !$relevantTitle ) {
2259 $relevantTitle = $mainContext->getSkin()->getRelevantTitle();
2260 }
2261 }
2262
2263 $isWatchlistExpiryEnabled = $config->get( MainConfigNames::WatchlistExpiry );
2264 if ( !$isWatchlistExpiryEnabled || !$relevantTitle || !$relevantTitle->canExist() ) {
2265 return;
2266 }
2267
2268 $watchStore = MediaWikiServices::getInstance()->getWatchedItemStore();
2269 $watchedItem = $watchStore->getWatchedItem( $user, $relevantTitle );
2270 if ( $watchedItem instanceof WatchedItem && $watchedItem->getExpiry() !== null ) {
2271 $diffInDays = $watchedItem->getExpiryInDays();
2272
2273 if ( $diffInDays ) {
2274 $msgParams = [ $diffInDays ];
2275 // Resolves to tooltip-ca-unwatch-expiring message
2276 $tooltip = 'ca-unwatch-expiring';
2277 } else { // Resolves to tooltip-ca-unwatch-expiring-hours message
2278 $tooltip = 'ca-unwatch-expiring-hours';
2279 }
2280 }
2281 }
2282
2299 public static function tooltipAndAccesskeyAttribs(
2300 $name,
2301 array $msgParams = [],
2302 $options = null,
2303 $localizer = null,
2304 $user = null,
2305 $config = null,
2306 $relevantTitle = null
2307 ) {
2308 $options = (array)$options;
2309 $options[] = 'withaccess';
2310 $tooltipTitle = $name;
2311
2312 // Get optional parameters from global context if any missing.
2313 if ( !$localizer ) {
2314 $localizer = self::getContextFromMain();
2315 }
2316
2317 // @since 1.35 - add a WatchlistExpiry feature flag to show new watchstar tooltip message
2318 if ( $name === 'ca-unwatch' ) {
2319 self::updateWatchstarTooltipMessage( $tooltipTitle, $msgParams, $config, $user, $relevantTitle );
2320 }
2321
2322 $attribs = [
2323 'title' => self::titleAttrib( $tooltipTitle, $options, $msgParams, $localizer ),
2324 'accesskey' => self::accesskey( $name, $localizer )
2325 ];
2326 if ( $attribs['title'] === false ) {
2327 unset( $attribs['title'] );
2328 }
2329 if ( $attribs['accesskey'] === false ) {
2330 unset( $attribs['accesskey'] );
2331 }
2332 return $attribs;
2333 }
2334
2342 public static function tooltip( $name, $options = null ) {
2343 $tooltip = self::titleAttrib( $name, $options );
2344 if ( $tooltip === false ) {
2345 return '';
2346 }
2347 return Xml::expandAttributes( [
2348 'title' => $tooltip
2349 ] );
2350 }
2351
2352}
const NS_USER
Definition Defines.php:66
const NS_FILE
Definition Defines.php:70
const NS_MAIN
Definition Defines.php:64
const PROTO_RELATIVE
Definition Defines.php:195
const NS_USER_TALK
Definition Defines.php:67
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
wfCgiToArray( $query)
This is the logical opposite of wfArrayToCgi(): it accepts a query string as its argument and returns...
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
$fallback
if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode $wgLang
Definition Setup.php:497
if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode $wgTitle
Definition Setup.php:497
if(!defined('MW_SETUP_CALLBACK'))
The persistent session ID (if any) loaded at startup.
Definition WebStart.php:82
An IContextSource implementation which will inherit context from another source but allow individual ...
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:30
Base class for language-specific code.
Definition Language.php:53
Some internal bits split of from Skin.php.
Definition Linker.php:42
static makeThumbLinkObj(LinkTarget $title, $file, $label='', $alt='', $align=null, $params=[], $framed=false, $manualthumb='')
Make HTML for a thumbnail including image, border and caption.
Definition Linker.php:594
static accesskey( $name, $localizer=null)
Given the id of an interface element, constructs the appropriate accesskey attribute from the system ...
Definition Linker.php:2128
static makeMediaLinkFile(LinkTarget $title, $file, $html='')
Create a direct link to a given uploaded file.
Definition Linker.php:993
static link( $target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition Linker.php:91
static tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition Linker.php:1664
static userLink( $userId, $userName, $altUserName=false)
Make user link (or user contributions for unregistered users)
Definition Linker.php:1114
static $accesskeycache
Definition Linker.php:2114
static expandLocalLinks(string $html)
Helper function to expand local links.
Definition Linker.php:1414
static makeBrokenImageLinkObj( $title, $label='', $query='', $unused1='', $unused2='', $time=false, array $handlerParams=[], bool $currentExists=false)
Make a "broken" link to an image.
Definition Linker.php:870
static getRevisionDeletedClass(RevisionRecord $revisionRecord)
Returns css class of a deleted revision.
Definition Linker.php:1351
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:1035
static getRollbackEditCount(RevisionRecord $revRecord, $verify)
This function will return the number of revisions which a rollback would revert and,...
Definition Linker.php:1893
static linkKnown( $target, $html=null, $customAttribs=[], $query=[], $options=[ 'known'])
Identical to link(), except $options defaults to 'known'.
Definition Linker.php:143
static makeExternalImage( $url, $alt='')
Return the code for images which were added via external links, via Parser::maybeMakeExternalImage().
Definition Linker.php:244
static revComment(RevisionRecord $revRecord, $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:1607
static revUserLink(RevisionRecord $revRecord, $isPublic=false)
Generate a user link if the current user is allowed to view it.
Definition Linker.php:1323
static processResponsiveImages( $file, $thumb, $hp)
Process responsive images: add 1.5x and 2x subimages to the thumbnail, where applicable.
Definition Linker.php:833
static blockLink( $userId, $userText)
Definition Linker.php:1276
static revDeleteLinkDisabled( $delete=true)
Creates a dead (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2229
static normalizeSubpageLink( $contextTitle, $target, &$text)
Definition Linker.php:1488
const TOOL_LINKS_NOBLOCK
Flags for userToolLinks()
Definition Linker.php:46
static getRevDeleteLink(Authority $performer, RevisionRecord $revRecord, LinkTarget $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
Definition Linker.php:2153
static makeSelfLinkObj( $nt, $html='', $query='', $trail='', $prefix='')
Make appropriate markup for a link to the current article.
Definition Linker.php:165
static getUploadUrl( $destFile, $query='')
Get the URL to upload a certain file.
Definition Linker.php:943
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:301
static tocIndent()
Add another level to the Table of Contents.
Definition Linker.php:1638
static makeThumbLink2(LinkTarget $title, $file, $frameParams=[], $handlerParams=[], $time=false, $query='', array $classes=[], ?Parser $parser=null)
Definition Linker.php:627
static getInvalidTitleDescription(IContextSource $context, $namespace, $title)
Get a message saying that an invalid title was encountered.
Definition Linker.php:189
static emailLink( $userId, $userText)
Definition Linker.php:1297
static generateRollback(RevisionRecord $revRecord, IContextSource $context=null, $options=[ 'verify'])
Generate a rollback link for a given revision.
Definition Linker.php:1834
static formatHiddenCategories( $hiddencats)
Returns HTML for the "hidden categories on this page" list.
Definition Linker.php:2035
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:1796
static generateTOC( $tree, Language $lang=null)
Generate a table of contents from a section tree.
Definition Linker.php:1737
static titleAttrib( $name, $options=null, array $msgParams=[], $localizer=null)
Given the id of an interface element, constructs the appropriate title attribute from the system mess...
Definition Linker.php:2080
const TOOL_LINKS_EMAIL
Definition Linker.php:47
static formatRevisionSize( $size)
Definition Linker.php:1623
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:1585
static tooltip( $name, $options=null)
Returns raw bits of HTML, use titleAttrib()
Definition Linker.php:2342
static userTalkLink( $userId, $userText)
Definition Linker.php:1254
static buildRollbackLink(RevisionRecord $revRecord, IContextSource $context=null, $editCount=false)
Build a raw rollback link, useful for collections of "tool" links.
Definition Linker.php:1962
static revUserTools(RevisionRecord $revRecord, $isPublic=false, $useParentheses=true)
Generate a user tool link cluster if the current user is allowed to view it.
Definition Linker.php:1371
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:1475
static makeMediaLinkObj( $title, $html='', $time=false)
Create a direct link to a given uploaded file.
Definition Linker.php:974
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition Linker.php:1061
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:1159
static normaliseSpecialPage(LinkTarget $target)
Definition Linker.php:210
static makeHeadline( $level, $attribs, $anchor, $html, $link, $fallbackAnchor=false)
Create a headline for content.
Definition Linker.php:1775
static tocUnindent( $level)
Finish one or more sublevels on the Table of Contents.
Definition Linker.php:1649
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:1700
static getImageLinkMTOParams( $frameParams, $query='', $parser=null)
Get the link parameters for MediaTransformOutput::toHtml() from given frame parameters supplied by th...
Definition Linker.php:551
static revDeleteLink( $query=[], $restricted=false, $delete=true)
Creates a (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2205
static tooltipAndAccesskeyAttribs( $name, array $msgParams=[], $options=null, $localizer=null, $user=null, $config=null, $relevantTitle=null)
Returns the attributes for the tooltip and access key.
Definition Linker.php:2299
static tocLineEnd()
End a Table Of Contents line.
Definition Linker.php:1688
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:1449
static userToolLinksRedContribs( $userId, $userText, $edits=null, $useParentheses=true)
Alias for userToolLinks( $userId, $userText, true );.
Definition Linker.php:1242
Basic media transform error class.
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
Page revision base class.
getUser( $audience=self::FOR_PUBLIC, Authority $performer=null)
Fetch revision's author's user identity, if it's available to the specified audience.
getVisibility()
Get the deletion bitfield of the revision.
getPageId( $wikiId=self::LOCAL)
Get the page ID.
getTimestamp()
MCR migration note: this replaced Revision::getTimestamp.
getPageAsLinkTarget()
Returns the title of the page this revision is associated with as a LinkTarget object.
userCan( $field, Authority $performer)
Determine if the give authority is allowed to view a particular field of this revision,...
isDeleted( $field)
MCR migration note: this replaced Revision::isDeleted.
getId( $wikiId=self::LOCAL)
Get revision ID.
static numParam( $num)
Definition Message.php:1145
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition Parser.php:96
getTargetLanguage()
Get the target language for the content being parsed.
Definition Parser.php:1184
getTitle()
Definition Parser.php:1037
getBadFileLookup()
Get the BadFileLookup instance that this Parser is using.
Definition Parser.php:1258
static getExternalLinkRel( $url=false, LinkTarget $title=null)
Get the rel attribute for a particular external link.
Definition Parser.php:2259
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 newFromId( $id)
Static factory method for creation from a given user ID.
Definition User.php:639
Representation of a pair of user and title for watchlist entries.
getExpiry(?int $style=TS_MW)
When the watched item will expire.
Note that none of the methods in this class are stable to override.
Interface for objects which can provide a MediaWiki context on request.
This interface represents the authority associated the current execution context, such as a web reque...
Definition Authority.php:37
isAllowed(string $permission)
Checks whether this authority has the given permission in general.
msg( $key,... $params)
This is the method for getting translated interface messages.
foreach( $mmfl['setupFiles'] as $fileName) if($queue) if(empty( $mmfl['quiet'])) $s
const DB_REPLICA
Definition defines.php:26
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