MediaWiki master
Linker.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\Linker;
24
25use File;
26use HtmlArmor;
27use Language;
49use Wikimedia\Assert\Assert;
50use Wikimedia\IPUtils;
51use Wikimedia\Parsoid\Core\TOCData;
53use Wikimedia\RemexHtml\Serializer\SerializerNode;
54use Xml;
55
65class Linker {
69 public const TOOL_LINKS_NOBLOCK = 1;
70 public const TOOL_LINKS_EMAIL = 2;
71
113 public static function link(
114 $target, $html = null, $customAttribs = [], $query = [], $options = []
115 ) {
116 if ( !$target instanceof LinkTarget ) {
117 wfWarn( __METHOD__ . ': Requires $target to be a LinkTarget object.', 2 );
118 return "<!-- ERROR -->$html";
119 }
120
121 $services = MediaWikiServices::getInstance();
122 $options = (array)$options;
123 if ( $options ) {
124 // Custom options, create new LinkRenderer
125 $linkRenderer = $services->getLinkRendererFactory()
126 ->createFromLegacyOptions( $options );
127 } else {
128 $linkRenderer = $services->getLinkRenderer();
129 }
130
131 if ( $html !== null ) {
132 $text = new HtmlArmor( $html );
133 } else {
134 $text = null;
135 }
136
137 if ( in_array( 'known', $options, true ) ) {
138 return $linkRenderer->makeKnownLink( $target, $text, $customAttribs, $query );
139 }
140
141 if ( in_array( 'broken', $options, true ) ) {
142 return $linkRenderer->makeBrokenLink( $target, $text, $customAttribs, $query );
143 }
144
145 if ( in_array( 'noclasses', $options, true ) ) {
146 return $linkRenderer->makePreloadedLink( $target, $text, '', $customAttribs, $query );
147 }
148
149 return $linkRenderer->makeLink( $target, $text, $customAttribs, $query );
150 }
151
171 public static function linkKnown(
172 $target, $html = null, $customAttribs = [],
173 $query = [], $options = [ 'known' ]
174 ) {
175 return self::link( $target, $html, $customAttribs, $query, $options );
176 }
177
195 public static function makeSelfLinkObj( $nt, $html = '', $query = '', $trail = '', $prefix = '', $hash = '' ) {
196 $nt = Title::newFromLinkTarget( $nt );
197 $attrs = [];
198 if ( $hash ) {
199 $attrs['class'] = 'mw-selflink-fragment';
200 $attrs['href'] = '#' . $hash;
201 } else {
202 // For backwards compatibility with gadgets we add selflink as well.
203 $attrs['class'] = 'mw-selflink selflink';
204 }
205 $ret = Html::rawElement( 'a', $attrs, $prefix . $html ) . $trail;
206 $hookRunner = new HookRunner( MediaWikiServices::getInstance()->getHookContainer() );
207 if ( !$hookRunner->onSelfLinkBegin( $nt, $html, $trail, $prefix, $ret ) ) {
208 return $ret;
209 }
210
211 if ( $html == '' ) {
212 $html = htmlspecialchars( $nt->getPrefixedText() );
213 }
214 [ $inside, $trail ] = self::splitTrail( $trail );
215 return Html::rawElement( 'a', $attrs, $prefix . $html . $inside ) . $trail;
216 }
217
228 public static function getInvalidTitleDescription( IContextSource $context, $namespace, $title ) {
229 // First we check whether the namespace exists or not.
230 if ( MediaWikiServices::getInstance()->getNamespaceInfo()->exists( $namespace ) ) {
231 if ( $namespace == NS_MAIN ) {
232 $name = $context->msg( 'blanknamespace' )->text();
233 } else {
234 $name = MediaWikiServices::getInstance()->getContentLanguage()->
235 getFormattedNsText( $namespace );
236 }
237 return $context->msg( 'invalidtitle-knownnamespace', $namespace, $name, $title )->text();
238 }
239
240 return $context->msg( 'invalidtitle-unknownnamespace', $namespace, $title )->text();
241 }
242
251 private static function fnamePart( $url ) {
252 $basename = strrchr( $url, '/' );
253 if ( $basename === false ) {
254 $basename = $url;
255 } else {
256 $basename = substr( $basename, 1 );
257 }
258 return $basename;
259 }
260
271 public static function makeExternalImage( $url, $alt = '' ) {
272 if ( $alt == '' ) {
273 $alt = self::fnamePart( $url );
274 }
275 $img = '';
276 $success = ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )
277 ->onLinkerMakeExternalImage( $url, $alt, $img );
278 if ( !$success ) {
279 wfDebug( "Hook LinkerMakeExternalImage changed the output of external image "
280 . "with url {$url} and alt text {$alt} to {$img}" );
281 return $img;
282 }
283 return Html::element( 'img',
284 [
285 'src' => $url,
286 'alt' => $alt
287 ]
288 );
289 }
290
329 public static function makeImageLink( Parser $parser, LinkTarget $title,
330 $file, $frameParams = [], $handlerParams = [], $time = false,
331 $query = '', $widthOption = null
332 ) {
333 $title = Title::newFromLinkTarget( $title );
334 $res = null;
335 // DummyLinker is deprecated since 1.42, $dummy will be replaced with null.
336 $dummy = new DummyLinker();
337 $hookRunner = new HookRunner( MediaWikiServices::getInstance()->getHookContainer() );
338 if ( !$hookRunner->onImageBeforeProduceHTML( $dummy, $title,
339 // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
340 $file, $frameParams, $handlerParams, $time, $res,
341 // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
342 $parser, $query, $widthOption )
343 ) {
344 return $res;
345 }
346
347 if ( $file && !$file->allowInlineDisplay() ) {
348 wfDebug( __METHOD__ . ': ' . $title->getPrefixedDBkey() . ' does not allow inline display' );
349 return self::link( $title );
350 }
351
352 // Clean up parameters
353 $page = $handlerParams['page'] ?? false;
354 if ( !isset( $frameParams['align'] ) ) {
355 $frameParams['align'] = '';
356 }
357 if ( !isset( $frameParams['title'] ) ) {
358 $frameParams['title'] = '';
359 }
360 if ( !isset( $frameParams['class'] ) ) {
361 $frameParams['class'] = '';
362 }
363
364 $services = MediaWikiServices::getInstance();
365 $config = $services->getMainConfig();
366 $enableLegacyMediaDOM = $config->get( MainConfigNames::ParserEnableLegacyMediaDOM );
367
368 $classes = [];
369 if (
370 !isset( $handlerParams['width'] ) &&
371 !isset( $frameParams['manualthumb'] ) &&
372 !isset( $frameParams['framed'] )
373 ) {
374 $classes[] = 'mw-default-size';
375 }
376
377 $prefix = $postfix = '';
378
379 if ( $enableLegacyMediaDOM ) {
380 if ( $frameParams['align'] == 'center' ) {
381 $prefix = '<div class="center">';
382 $postfix = '</div>';
383 $frameParams['align'] = 'none';
384 }
385 }
386
387 if ( $file && !isset( $handlerParams['width'] ) ) {
388 if ( isset( $handlerParams['height'] ) && $file->isVectorized() ) {
389 // If its a vector image, and user only specifies height
390 // we don't want it to be limited by its "normal" width.
391 $svgMaxSize = $config->get( MainConfigNames::SVGMaxSize );
392 $handlerParams['width'] = $svgMaxSize;
393 } else {
394 $handlerParams['width'] = $file->getWidth( $page );
395 }
396
397 if ( isset( $frameParams['thumbnail'] )
398 || isset( $frameParams['manualthumb'] )
399 || isset( $frameParams['framed'] )
400 || isset( $frameParams['frameless'] )
401 || !$handlerParams['width']
402 ) {
403 $thumbLimits = $config->get( MainConfigNames::ThumbLimits );
404 $thumbUpright = $config->get( MainConfigNames::ThumbUpright );
405 if ( $widthOption === null || !isset( $thumbLimits[$widthOption] ) ) {
406 $userOptionsLookup = $services->getUserOptionsLookup();
407 $widthOption = $userOptionsLookup->getDefaultOption( 'thumbsize' );
408 }
409
410 // Reduce width for upright images when parameter 'upright' is used
411 if ( isset( $frameParams['upright'] ) && $frameParams['upright'] == 0 ) {
412 $frameParams['upright'] = $thumbUpright;
413 }
414
415 // For caching health: If width scaled down due to upright
416 // parameter, round to full __0 pixel to avoid the creation of a
417 // lot of odd thumbs.
418 $prefWidth = isset( $frameParams['upright'] ) ?
419 round( $thumbLimits[$widthOption] * $frameParams['upright'], -1 ) :
420 $thumbLimits[$widthOption];
421
422 // Use width which is smaller: real image width or user preference width
423 // Unless image is scalable vector.
424 if ( !isset( $handlerParams['height'] ) && ( $handlerParams['width'] <= 0 ||
425 $prefWidth < $handlerParams['width'] || $file->isVectorized() ) ) {
426 $handlerParams['width'] = $prefWidth;
427 }
428 }
429 }
430
431 // Parser::makeImage has a similarly named variable
432 $hasVisibleCaption = isset( $frameParams['thumbnail'] ) ||
433 isset( $frameParams['manualthumb'] ) ||
434 isset( $frameParams['framed'] );
435
436 if ( $hasVisibleCaption ) {
437 if ( $enableLegacyMediaDOM ) {
438 // This is no longer needed in our new media output, since the
439 // default styling in content.media-common.less takes care of it;
440 // see T269704.
441
442 # Create a thumbnail. Alignment depends on the writing direction of
443 # the page content language (right-aligned for LTR languages,
444 # left-aligned for RTL languages)
445 # If a thumbnail width has not been provided, it is set
446 # to the default user option as specified in Language*.php
447 if ( $frameParams['align'] == '' ) {
448 $frameParams['align'] = $parser->getTargetLanguage()->alignEnd();
449 }
450 }
451 return $prefix . self::makeThumbLink2(
452 $title, $file, $frameParams, $handlerParams, $time, $query,
453 $classes, $parser
454 ) . $postfix;
455 }
456
457 $rdfaType = 'mw:File';
458
459 if ( isset( $frameParams['frameless'] ) ) {
460 $rdfaType .= '/Frameless';
461 if ( $file ) {
462 $srcWidth = $file->getWidth( $page );
463 # For "frameless" option: do not present an image bigger than the
464 # source (for bitmap-style images). This is the same behavior as the
465 # "thumb" option does it already.
466 if ( $srcWidth && !$file->mustRender() && $handlerParams['width'] > $srcWidth ) {
467 $handlerParams['width'] = $srcWidth;
468 }
469 }
470 }
471
472 if ( $file && isset( $handlerParams['width'] ) ) {
473 # Create a resized image, without the additional thumbnail features
474 $thumb = $file->transform( $handlerParams );
475 } else {
476 $thumb = false;
477 }
478
479 $isBadFile = $file && $thumb &&
480 $parser->getBadFileLookup()->isBadFile( $title->getDBkey(), $parser->getTitle() );
481
482 if ( !$thumb || ( !$enableLegacyMediaDOM && $thumb->isError() ) || $isBadFile ) {
483 $rdfaType = 'mw:Error ' . $rdfaType;
484 $currentExists = $file && $file->exists();
485 if ( $enableLegacyMediaDOM ) {
486 $label = $frameParams['title'];
487 } else {
488 if ( $currentExists && !$thumb ) {
489 $label = wfMessage( 'thumbnail_error', '' )->text();
490 } elseif ( $thumb && $thumb->isError() ) {
491 Assert::invariant(
492 $thumb instanceof MediaTransformError,
493 'Unknown MediaTransformOutput: ' . get_class( $thumb )
494 );
495 $label = $thumb->toText();
496 } else {
497 $label = $frameParams['alt'] ?? '';
498 }
499 }
501 $title, $label, '', '', '', (bool)$time, $handlerParams, $currentExists
502 );
503 } else {
504 self::processResponsiveImages( $file, $thumb, $handlerParams );
505 $params = [];
506 // An empty alt indicates an image is not a key part of the content
507 // and that non-visual browsers may omit it from rendering. Only
508 // set the parameter if it's explicitly requested.
509 if ( isset( $frameParams['alt'] ) ) {
510 $params['alt'] = $frameParams['alt'];
511 }
512 $params['title'] = $frameParams['title'];
513 if ( $enableLegacyMediaDOM ) {
514 $params += [
515 'valign' => $frameParams['valign'] ?? false,
516 'img-class' => $frameParams['class'],
517 ];
518 if ( isset( $frameParams['border'] ) ) {
519 $params['img-class'] .= ( $params['img-class'] !== '' ? ' ' : '' ) . 'thumbborder';
520 }
521 } else {
522 $params += [
523 'img-class' => 'mw-file-element',
524 ];
525 }
526 $params = self::getImageLinkMTOParams( $frameParams, $query, $parser ) + $params;
527 $s = $thumb->toHtml( $params );
528 }
529
530 if ( $enableLegacyMediaDOM ) {
531 if ( $frameParams['align'] != '' ) {
532 $s = Html::rawElement(
533 'div',
534 [ 'class' => 'float' . $frameParams['align'] ],
535 $s
536 );
537 }
538 return str_replace( "\n", ' ', $prefix . $s . $postfix );
539 }
540
541 $wrapper = 'span';
542 $caption = '';
543
544 if ( $frameParams['align'] != '' ) {
545 $wrapper = 'figure';
546 // Possible values: mw-halign-left mw-halign-center mw-halign-right mw-halign-none
547 $classes[] = "mw-halign-{$frameParams['align']}";
548 $caption = Html::rawElement(
549 'figcaption', [], $frameParams['caption'] ?? ''
550 );
551 } elseif ( isset( $frameParams['valign'] ) ) {
552 // Possible values: mw-valign-middle mw-valign-baseline mw-valign-sub
553 // mw-valign-super mw-valign-top mw-valign-text-top mw-valign-bottom
554 // mw-valign-text-bottom
555 $classes[] = "mw-valign-{$frameParams['valign']}";
556 }
557
558 if ( isset( $frameParams['border'] ) ) {
559 $classes[] = 'mw-image-border';
560 }
561
562 if ( isset( $frameParams['class'] ) ) {
563 $classes[] = $frameParams['class'];
564 }
565
566 $attribs = [
567 'class' => $classes,
568 'typeof' => $rdfaType,
569 ];
570
571 $s = Html::rawElement( $wrapper, $attribs, $s . $caption );
572
573 return str_replace( "\n", ' ', $s );
574 }
575
584 public static function getImageLinkMTOParams( $frameParams, $query = '', $parser = null ) {
585 $mtoParams = [];
586 if ( isset( $frameParams['link-url'] ) && $frameParams['link-url'] !== '' ) {
587 $mtoParams['custom-url-link'] = $frameParams['link-url'];
588 if ( isset( $frameParams['link-target'] ) ) {
589 $mtoParams['custom-target-link'] = $frameParams['link-target'];
590 }
591 if ( $parser ) {
592 $extLinkAttrs = $parser->getExternalLinkAttribs( $frameParams['link-url'] );
593 foreach ( $extLinkAttrs as $name => $val ) {
594 // Currently could include 'rel' and 'target'
595 $mtoParams['parser-extlink-' . $name] = $val;
596 }
597 }
598 } elseif ( isset( $frameParams['link-title'] ) && $frameParams['link-title'] !== '' ) {
599 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
600 $mtoParams['custom-title-link'] = Title::newFromLinkTarget(
601 $linkRenderer->normalizeTarget( $frameParams['link-title'] )
602 );
603 if ( isset( $frameParams['link-title-query'] ) ) {
604 $mtoParams['custom-title-link-query'] = $frameParams['link-title-query'];
605 }
606 } elseif ( !empty( $frameParams['no-link'] ) ) {
607 // No link
608 } else {
609 $mtoParams['desc-link'] = true;
610 $mtoParams['desc-query'] = $query;
611 }
612 return $mtoParams;
613 }
614
627 public static function makeThumbLinkObj(
628 LinkTarget $title, $file, $label = '', $alt = '', $align = null,
629 $params = [], $framed = false, $manualthumb = ''
630 ) {
631 $frameParams = [
632 'alt' => $alt,
633 'caption' => $label,
634 'align' => $align
635 ];
636 $classes = [];
637 if ( $manualthumb ) {
638 $frameParams['manualthumb'] = $manualthumb;
639 } elseif ( $framed ) {
640 $frameParams['framed'] = true;
641 } elseif ( !isset( $params['width'] ) ) {
642 $classes[] = 'mw-default-size';
643 }
645 $title, $file, $frameParams, $params, false, '', $classes
646 );
647 }
648
660 public static function makeThumbLink2(
661 LinkTarget $title, $file, $frameParams = [], $handlerParams = [],
662 $time = false, $query = '', array $classes = [], ?Parser $parser = null
663 ) {
664 $exists = $file && $file->exists();
665
666 $services = MediaWikiServices::getInstance();
667 $enableLegacyMediaDOM = $services->getMainConfig()->get( MainConfigNames::ParserEnableLegacyMediaDOM );
668
669 $page = $handlerParams['page'] ?? false;
670 $lang = $handlerParams['lang'] ?? false;
671
672 if ( !isset( $frameParams['align'] ) ) {
673 $frameParams['align'] = '';
674 if ( $enableLegacyMediaDOM ) {
675 $frameParams['align'] = 'right';
676 }
677 }
678 if ( !isset( $frameParams['caption'] ) ) {
679 $frameParams['caption'] = '';
680 }
681
682 if ( empty( $handlerParams['width'] ) ) {
683 // Reduce width for upright images when parameter 'upright' is used
684 $handlerParams['width'] = isset( $frameParams['upright'] ) ? 130 : 180;
685 }
686
687 $thumb = false;
688 $noscale = false;
689 $manualthumb = false;
690 $manual_title = '';
691 $rdfaType = 'mw:File/Thumb';
692
693 if ( !$exists ) {
694 // Same precedence as the $exists case
695 if ( !isset( $frameParams['manualthumb'] ) && isset( $frameParams['framed'] ) ) {
696 $rdfaType = 'mw:File/Frame';
697 }
698 $outerWidth = $handlerParams['width'] + 2;
699 } else {
700 if ( isset( $frameParams['manualthumb'] ) ) {
701 # Use manually specified thumbnail
702 $manual_title = Title::makeTitleSafe( NS_FILE, $frameParams['manualthumb'] );
703 if ( $manual_title ) {
704 $manual_img = $services->getRepoGroup()
705 ->findFile( $manual_title );
706 if ( $manual_img ) {
707 $thumb = $manual_img->getUnscaledThumb( $handlerParams );
708 $manualthumb = true;
709 }
710 }
711 } else {
712 $srcWidth = $file->getWidth( $page );
713 if ( isset( $frameParams['framed'] ) ) {
714 $rdfaType = 'mw:File/Frame';
715 if ( !$file->isVectorized() ) {
716 // Use image dimensions, don't scale
717 $noscale = true;
718 } else {
719 // framed is unscaled, but for vectorized images
720 // we need to a width for scaling up for the high density variants
721 $handlerParams['width'] = $srcWidth;
722 }
723 }
724
725 // Do not present an image bigger than the source, for bitmap-style images
726 // This is a hack to maintain compatibility with arbitrary pre-1.10 behavior
727 if ( $srcWidth && !$file->mustRender() && $handlerParams['width'] > $srcWidth ) {
728 $handlerParams['width'] = $srcWidth;
729 }
730
731 $thumb = $noscale
732 ? $file->getUnscaledThumb( $handlerParams )
733 : $file->transform( $handlerParams );
734 }
735
736 if ( $thumb ) {
737 $outerWidth = $thumb->getWidth() + 2;
738 } else {
739 $outerWidth = $handlerParams['width'] + 2;
740 }
741 }
742
743 if ( !$enableLegacyMediaDOM && $parser && $rdfaType === 'mw:File/Thumb' ) {
744 $parser->getOutput()->addModules( [ 'mediawiki.page.media' ] );
745 }
746
747 $url = Title::newFromLinkTarget( $title )->getLocalURL( $query );
748 $linkTitleQuery = [];
749 if ( $page || $lang ) {
750 if ( $page ) {
751 $linkTitleQuery['page'] = $page;
752 }
753 if ( $lang ) {
754 $linkTitleQuery['lang'] = $lang;
755 }
756 # ThumbnailImage::toHtml() already adds page= onto the end of DjVu URLs
757 # So we don't need to pass it here in $query. However, the URL for the
758 # zoom icon still needs it, so we make a unique query for it. See T16771
759 $url = wfAppendQuery( $url, $linkTitleQuery );
760 }
761
762 if ( $manualthumb
763 && !isset( $frameParams['link-title'] )
764 && !isset( $frameParams['link-url'] )
765 && !isset( $frameParams['no-link'] ) ) {
766 $frameParams['link-title'] = $title;
767 $frameParams['link-title-query'] = $linkTitleQuery;
768 }
769
770 if ( $frameParams['align'] != '' ) {
771 // Possible values: mw-halign-left mw-halign-center mw-halign-right mw-halign-none
772 $classes[] = "mw-halign-{$frameParams['align']}";
773 }
774
775 if ( isset( $frameParams['class'] ) ) {
776 $classes[] = $frameParams['class'];
777 }
778
779 $s = '';
780
781 if ( $enableLegacyMediaDOM ) {
782 $s .= "<div class=\"thumb t{$frameParams['align']}\">"
783 . "<div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
784 }
785
786 $isBadFile = $exists && $thumb && $parser &&
787 $parser->getBadFileLookup()->isBadFile(
788 $manualthumb ? $manual_title : $title->getDBkey(),
789 $parser->getTitle()
790 );
791
792 if ( !$exists ) {
793 $rdfaType = 'mw:Error ' . $rdfaType;
794 $label = '';
795 if ( !$enableLegacyMediaDOM ) {
796 $label = $frameParams['alt'] ?? '';
797 }
799 $title, $label, '', '', '', (bool)$time, $handlerParams, false
800 );
801 $zoomIcon = '';
802 } elseif ( !$thumb || ( !$enableLegacyMediaDOM && $thumb->isError() ) || $isBadFile ) {
803 $rdfaType = 'mw:Error ' . $rdfaType;
804 if ( $enableLegacyMediaDOM ) {
805 if ( !$thumb ) {
806 $s .= wfMessage( 'thumbnail_error', '' )->escaped();
807 } else {
809 $title, '', '', '', '', (bool)$time, $handlerParams, true
810 );
811 }
812 } else {
813 if ( $thumb && $thumb->isError() ) {
814 Assert::invariant(
815 $thumb instanceof MediaTransformError,
816 'Unknown MediaTransformOutput: ' . get_class( $thumb )
817 );
818 $label = $thumb->toText();
819 } elseif ( !$thumb ) {
820 $label = wfMessage( 'thumbnail_error', '' )->text();
821 } else {
822 $label = '';
823 }
825 $title, $label, '', '', '', (bool)$time, $handlerParams, true
826 );
827 }
828 $zoomIcon = '';
829 } else {
830 if ( !$noscale && !$manualthumb ) {
831 self::processResponsiveImages( $file, $thumb, $handlerParams );
832 }
833 $params = [];
834 // An empty alt indicates an image is not a key part of the content
835 // and that non-visual browsers may omit it from rendering. Only
836 // set the parameter if it's explicitly requested.
837 if ( isset( $frameParams['alt'] ) ) {
838 $params['alt'] = $frameParams['alt'];
839 }
840 if ( $enableLegacyMediaDOM ) {
841 $params += [
842 'img-class' => ( isset( $frameParams['class'] ) && $frameParams['class'] !== ''
843 ? $frameParams['class'] . ' '
844 : '' ) . 'thumbimage'
845 ];
846 } else {
847 $params += [
848 'img-class' => 'mw-file-element',
849 ];
850 // Only thumbs gets the magnify link
851 if ( $rdfaType === 'mw:File/Thumb' ) {
852 $params['magnify-resource'] = $url;
853 }
854 }
855 $params = self::getImageLinkMTOParams( $frameParams, $query, $parser ) + $params;
856 $s .= $thumb->toHtml( $params );
857 if ( isset( $frameParams['framed'] ) ) {
858 $zoomIcon = '';
859 } else {
860 $zoomIcon = Html::rawElement( 'div', [ 'class' => 'magnify' ],
861 Html::rawElement( 'a', [
862 'href' => $url,
863 'class' => 'internal',
864 'title' => wfMessage( 'thumbnail-more' )->text(),
865 ] )
866 );
867 }
868 }
869
870 if ( $enableLegacyMediaDOM ) {
871 $s .= ' <div class="thumbcaption">' . $zoomIcon . $frameParams['caption'] . '</div></div></div>';
872 return str_replace( "\n", ' ', $s );
873 }
874
875 $s .= Html::rawElement(
876 'figcaption', [], $frameParams['caption'] ?? ''
877 );
878
879 $attribs = [
880 'class' => $classes,
881 'typeof' => $rdfaType,
882 ];
883
884 $s = Html::rawElement( 'figure', $attribs, $s );
885
886 return str_replace( "\n", ' ', $s );
887 }
888
897 public static function processResponsiveImages( $file, $thumb, $hp ) {
898 $responsiveImages = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::ResponsiveImages );
899 if ( $responsiveImages && $thumb && !$thumb->isError() ) {
900 $hp15 = $hp;
901 $hp15['width'] = round( $hp['width'] * 1.5 );
902 $hp20 = $hp;
903 $hp20['width'] = $hp['width'] * 2;
904 if ( isset( $hp['height'] ) ) {
905 $hp15['height'] = round( $hp['height'] * 1.5 );
906 $hp20['height'] = $hp['height'] * 2;
907 }
908
909 $thumb15 = $file->transform( $hp15 );
910 $thumb20 = $file->transform( $hp20 );
911 if ( $thumb15 && !$thumb15->isError() && $thumb15->getUrl() !== $thumb->getUrl() ) {
912 $thumb->responsiveUrls['1.5'] = $thumb15->getUrl();
913 }
914 if ( $thumb20 && !$thumb20->isError() && $thumb20->getUrl() !== $thumb->getUrl() ) {
915 $thumb->responsiveUrls['2'] = $thumb20->getUrl();
916 }
917 }
918 }
919
934 public static function makeBrokenImageLinkObj(
935 $title, $label = '', $query = '', $unused1 = '', $unused2 = '',
936 $time = false, array $handlerParams = [], bool $currentExists = false
937 ) {
938 if ( !$title instanceof LinkTarget ) {
939 wfWarn( __METHOD__ . ': Requires $title to be a LinkTarget object.' );
940 return "<!-- ERROR -->" . htmlspecialchars( $label );
941 }
942
943 $title = Title::newFromLinkTarget( $title );
944 $services = MediaWikiServices::getInstance();
945 $mainConfig = $services->getMainConfig();
946 $enableUploads = $mainConfig->get( MainConfigNames::EnableUploads );
947 $uploadMissingFileUrl = $mainConfig->get( MainConfigNames::UploadMissingFileUrl );
948 $uploadNavigationUrl = $mainConfig->get( MainConfigNames::UploadNavigationUrl );
949 if ( $label == '' ) {
950 $label = $title->getPrefixedText();
951 }
952
953 $html = Html::element( 'span', [
954 'class' => 'mw-file-element mw-broken-media',
955 // These data attributes are used to dynamically size the span, see T273013
956 'data-width' => $handlerParams['width'] ?? null,
957 'data-height' => $handlerParams['height'] ?? null,
958 ], $label );
959
960 if ( $mainConfig->get( MainConfigNames::ParserEnableLegacyMediaDOM ) ) {
961 $html = htmlspecialchars( $label, ENT_COMPAT );
962 }
963
964 $repoGroup = $services->getRepoGroup();
965 $currentExists = $currentExists ||
966 ( $time && $repoGroup->findFile( $title ) !== false );
967
968 if ( ( $uploadMissingFileUrl || $uploadNavigationUrl || $enableUploads )
969 && !$currentExists
970 ) {
971 if (
972 $title->inNamespace( NS_FILE ) &&
973 $repoGroup->getLocalRepo()->checkRedirect( $title )
974 ) {
975 // We already know it's a redirect, so mark it accordingly
976 return self::link(
977 $title,
978 $html,
979 [ 'class' => 'mw-redirect' ],
980 wfCgiToArray( $query ),
981 [ 'known', 'noclasses' ]
982 );
983 }
984 return Html::rawElement( 'a', [
985 'href' => self::getUploadUrl( $title, $query ),
986 'class' => 'new',
987 'title' => $title->getPrefixedText()
988 ], $html );
989 }
990 return self::link(
991 $title,
992 $html,
993 [],
994 wfCgiToArray( $query ),
995 [ 'known', 'noclasses' ]
996 );
997 }
998
1007 public static function getUploadUrl( $destFile, $query = '' ) {
1008 $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
1009 $uploadMissingFileUrl = $mainConfig->get( MainConfigNames::UploadMissingFileUrl );
1010 $uploadNavigationUrl = $mainConfig->get( MainConfigNames::UploadNavigationUrl );
1011 $q = 'wpDestFile=' . Title::newFromLinkTarget( $destFile )->getPartialURL();
1012 if ( $query != '' ) {
1013 $q .= '&' . $query;
1014 }
1015
1016 if ( $uploadMissingFileUrl ) {
1017 return wfAppendQuery( $uploadMissingFileUrl, $q );
1018 }
1019
1020 if ( $uploadNavigationUrl ) {
1021 return wfAppendQuery( $uploadNavigationUrl, $q );
1022 }
1023
1024 $upload = SpecialPage::getTitleFor( 'Upload' );
1025
1026 return $upload->getLocalURL( $q );
1027 }
1028
1038 public static function makeMediaLinkObj( $title, $html = '', $time = false ) {
1039 $img = MediaWikiServices::getInstance()->getRepoGroup()->findFile(
1040 $title, [ 'time' => $time ]
1041 );
1042 return self::makeMediaLinkFile( $title, $img, $html );
1043 }
1044
1057 public static function makeMediaLinkFile( LinkTarget $title, $file, $html = '' ) {
1058 if ( $file && $file->exists() ) {
1059 $url = $file->getUrl();
1060 $class = 'internal';
1061 } else {
1062 $url = self::getUploadUrl( $title );
1063 $class = 'new';
1064 }
1065
1066 $alt = $title->getText();
1067 if ( $html == '' ) {
1068 $html = $alt;
1069 }
1070
1071 $ret = '';
1072 $attribs = [
1073 'href' => $url,
1074 'class' => $class,
1075 'title' => $alt
1076 ];
1077
1078 if ( !( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )->onLinkerMakeMediaLinkFile(
1079 Title::newFromLinkTarget( $title ), $file, $html, $attribs, $ret )
1080 ) {
1081 wfDebug( "Hook LinkerMakeMediaLinkFile changed the output of link "
1082 . "with url {$url} and text {$html} to {$ret}" );
1083 return $ret;
1084 }
1085
1086 return Html::rawElement( 'a', $attribs, $html );
1087 }
1088
1099 public static function specialLink( $name, $key = '' ) {
1100 $queryPos = strpos( $name, '?' );
1101 if ( $queryPos !== false ) {
1102 $getParams = wfCgiToArray( substr( $name, $queryPos + 1 ) );
1103 $name = substr( $name, 0, $queryPos );
1104 } else {
1105 $getParams = [];
1106 }
1107
1108 $slashPos = strpos( $name, '/' );
1109 if ( $slashPos !== false ) {
1110 $subpage = substr( $name, $slashPos + 1 );
1111 $name = substr( $name, 0, $slashPos );
1112 } else {
1113 $subpage = false;
1114 }
1115
1116 if ( $key == '' ) {
1117 $key = strtolower( $name );
1118 }
1119
1120 return self::linkKnown(
1121 SpecialPage::getTitleFor( $name, $subpage ),
1122 wfMessage( $key )->escaped(),
1123 [],
1124 $getParams
1125 );
1126 }
1127
1146 public static function makeExternalLink( $url, $text, $escape = true,
1147 $linktype = '', $attribs = [], $title = null
1148 ) {
1149 global $wgTitle;
1150 $class = 'external';
1151 if ( $linktype ) {
1152 $class .= " $linktype";
1153 }
1154 if ( isset( $attribs['class'] ) && $attribs['class'] ) {
1155 $class .= " {$attribs['class']}";
1156 }
1157 $attribs['class'] = $class;
1158
1159 if ( $escape ) {
1160 $text = htmlspecialchars( $text, ENT_COMPAT );
1161 }
1162
1163 if ( !$title ) {
1164 $title = $wgTitle;
1165 }
1166 $newRel = Parser::getExternalLinkRel( $url, $title );
1167 if ( !isset( $attribs['rel'] ) || $attribs['rel'] === '' ) {
1168 $attribs['rel'] = $newRel;
1169 } elseif ( $newRel !== null ) {
1170 // Merge the rel attributes.
1171 $newRels = explode( ' ', $newRel );
1172 $oldRels = explode( ' ', $attribs['rel'] );
1173 $combined = array_unique( array_merge( $newRels, $oldRels ) );
1174 $attribs['rel'] = implode( ' ', $combined );
1175 }
1176 $link = '';
1177 $success = ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )->onLinkerMakeExternalLink(
1178 $url, $text, $link, $attribs, $linktype );
1179 if ( !$success ) {
1180 wfDebug( "Hook LinkerMakeExternalLink changed the output of link "
1181 . "with url {$url} and text {$text} to {$link}" );
1182 return $link;
1183 }
1184 $attribs['href'] = $url;
1185 return Html::rawElement( 'a', $attribs, $text );
1186 }
1187
1200 public static function userLink(
1201 $userId,
1202 $userName,
1203 $altUserName = false,
1204 $attributes = []
1205 ) {
1206 if ( $userName === '' || $userName === false || $userName === null ) {
1207 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1208 'that need to be fixed?' );
1209 return wfMessage( 'empty-username' )->parse();
1210 }
1211
1212 $classes = 'mw-userlink';
1213 if ( MediaWikiServices::getInstance()->getTempUserConfig()->isTempName( $userName ) ) {
1214 $classes .= ' mw-tempuserlink';
1215 $page = SpecialPage::getTitleValueFor( 'Contributions', $userName );
1216 } elseif ( $userId == 0 ) {
1217 $page = ExternalUserNames::getUserLinkTitle( $userName );
1218
1219 if ( ExternalUserNames::isExternal( $userName ) ) {
1220 $classes .= ' mw-extuserlink';
1221 } elseif ( $altUserName === false ) {
1222 $altUserName = IPUtils::prettifyIP( $userName );
1223 }
1224 $classes .= ' mw-anonuserlink'; // Separate link class for anons (T45179)
1225 } else {
1226 $page = TitleValue::tryNew( NS_USER, strtr( $userName, ' ', '_' ) );
1227 }
1228
1229 // Wrap the output with <bdi> tags for directionality isolation
1230 $linkText =
1231 '<bdi>' . htmlspecialchars( $altUserName !== false ? $altUserName : $userName ) . '</bdi>';
1232
1233 if ( isset( $attributes['class'] ) ) {
1234 $attributes['class'] .= ' ' . $classes;
1235 } else {
1236 $attributes['class'] = $classes;
1237 }
1238
1239 return $page
1240 ? self::link( $page, $linkText, $attributes )
1241 : Html::rawElement( 'span', $attributes, $linkText );
1242 }
1243
1262 public static function userToolLinkArray(
1263 $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits = null
1264 ): array {
1265 $services = MediaWikiServices::getInstance();
1266 $disableAnonTalk = $services->getMainConfig()->get( MainConfigNames::DisableAnonTalk );
1267 $talkable = !( $disableAnonTalk && $userId == 0 );
1268 $blockable = !( $flags & self::TOOL_LINKS_NOBLOCK );
1269 $addEmailLink = $flags & self::TOOL_LINKS_EMAIL && $userId;
1270
1271 if ( $userId == 0 && ExternalUserNames::isExternal( $userText ) ) {
1272 // No tools for an external user
1273 return [];
1274 }
1275
1276 $items = [];
1277 if ( $talkable ) {
1278 $items[] = self::userTalkLink( $userId, $userText );
1279 }
1280 if ( $userId ) {
1281 // check if the user has an edit
1282 $attribs = [];
1283 $attribs['class'] = 'mw-usertoollinks-contribs';
1284 if ( $redContribsWhenNoEdits ) {
1285 if ( $edits === null ) {
1286 $user = UserIdentityValue::newRegistered( $userId, $userText );
1287 $edits = $services->getUserEditTracker()->getUserEditCount( $user );
1288 }
1289 if ( $edits === 0 ) {
1290 // Note: "new" class is inappropriate here, as "new" class
1291 // should only be used for pages that do not exist.
1292 $attribs['class'] .= ' mw-usertoollinks-contribs-no-edits';
1293 }
1294 }
1295 $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText );
1296
1297 $items[] = self::link( $contribsPage, wfMessage( 'contribslink' )->escaped(), $attribs );
1298 }
1299 $userCanBlock = RequestContext::getMain()->getAuthority()->isAllowed( 'block' );
1300 if ( $blockable && $userCanBlock ) {
1301 $items[] = self::blockLink( $userId, $userText );
1302 }
1303
1304 $user = RequestContext::getMain()->getUser();
1305 if ( $addEmailLink && $user->canSendEmail() ) {
1306 $items[] = self::emailLink( $userId, $userText );
1307 }
1308
1309 ( new HookRunner( $services->getHookContainer() ) )->onUserToolLinksEdit( $userId, $userText, $items );
1310
1311 return $items;
1312 }
1313
1321 public static function renderUserToolLinksArray( array $items, bool $useParentheses ): string {
1322 global $wgLang;
1323
1324 if ( !$items ) {
1325 return '';
1326 }
1327
1328 if ( $useParentheses ) {
1329 return wfMessage( 'word-separator' )->escaped()
1330 . '<span class="mw-usertoollinks">'
1331 . wfMessage( 'parentheses' )->rawParams( $wgLang->pipeList( $items ) )->escaped()
1332 . '</span>';
1333 }
1334
1335 $tools = [];
1336 foreach ( $items as $tool ) {
1337 $tools[] = Html::rawElement( 'span', [], $tool );
1338 }
1339 return ' <span class="mw-usertoollinks mw-changeslist-links">' .
1340 implode( ' ', $tools ) . '</span>';
1341 }
1342
1357 public static function userToolLinks(
1358 $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits = null,
1359 $useParentheses = true
1360 ) {
1361 if ( $userText === '' ) {
1362 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1363 'that need to be fixed?' );
1364 return ' ' . wfMessage( 'empty-username' )->parse();
1365 }
1366
1367 $items = self::userToolLinkArray( $userId, $userText, $redContribsWhenNoEdits, $flags, $edits );
1368 return self::renderUserToolLinksArray( $items, $useParentheses );
1369 }
1370
1380 public static function userToolLinksRedContribs(
1381 $userId, $userText, $edits = null, $useParentheses = true
1382 ) {
1383 return self::userToolLinks( $userId, $userText, true, 0, $edits, $useParentheses );
1384 }
1385
1392 public static function userTalkLink( $userId, $userText ) {
1393 if ( $userText === '' ) {
1394 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1395 'that need to be fixed?' );
1396 return wfMessage( 'empty-username' )->parse();
1397 }
1398
1399 $userTalkPage = TitleValue::tryNew( NS_USER_TALK, strtr( $userText, ' ', '_' ) );
1400 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-talk' ];
1401 $linkText = wfMessage( 'talkpagelinktext' )->escaped();
1402
1403 return $userTalkPage
1404 ? self::link( $userTalkPage, $linkText, $moreLinkAttribs )
1405 : Html::rawElement( 'span', $moreLinkAttribs, $linkText );
1406 }
1407
1414 public static function blockLink( $userId, $userText ) {
1415 if ( $userText === '' ) {
1416 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1417 'that need to be fixed?' );
1418 return wfMessage( 'empty-username' )->parse();
1419 }
1420
1421 $blockPage = SpecialPage::getTitleFor( 'Block', $userText );
1422 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-block' ];
1423
1424 return self::link( $blockPage,
1425 wfMessage( 'blocklink' )->escaped(),
1426 $moreLinkAttribs
1427 );
1428 }
1429
1435 public static function emailLink( $userId, $userText ) {
1436 if ( $userText === '' ) {
1437 wfLogWarning( __METHOD__ . ' received an empty username. Are there database errors ' .
1438 'that need to be fixed?' );
1439 return wfMessage( 'empty-username' )->parse();
1440 }
1441
1442 $emailPage = SpecialPage::getTitleFor( 'Emailuser', $userText );
1443 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-mail' ];
1444 return self::link( $emailPage,
1445 wfMessage( 'emaillink' )->escaped(),
1446 $moreLinkAttribs
1447 );
1448 }
1449
1461 public static function revUserLink( RevisionRecord $revRecord, $isPublic = false ) {
1462 // TODO inject authority
1463 $authority = RequestContext::getMain()->getAuthority();
1464
1465 $revUser = $revRecord->getUser(
1466 $isPublic ? RevisionRecord::FOR_PUBLIC : RevisionRecord::FOR_THIS_USER,
1467 $authority
1468 );
1469 if ( $revUser ) {
1470 $link = self::userLink( $revUser->getId(), $revUser->getName() );
1471 } else {
1472 // User is deleted and we can't (or don't want to) view it
1473 $link = wfMessage( 'rev-deleted-user' )->escaped();
1474 }
1475
1476 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
1477 $class = self::getRevisionDeletedClass( $revRecord );
1478 return '<span class="' . $class . '">' . $link . '</span>';
1479 }
1480 return $link;
1481 }
1482
1489 public static function getRevisionDeletedClass( RevisionRecord $revisionRecord ): string {
1490 $class = 'history-deleted';
1491 if ( $revisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
1492 $class .= ' mw-history-suppressed';
1493 }
1494 return $class;
1495 }
1496
1509 public static function revUserTools(
1510 RevisionRecord $revRecord,
1511 $isPublic = false,
1512 $useParentheses = true
1513 ) {
1514 // TODO inject authority
1515 $authority = RequestContext::getMain()->getAuthority();
1516
1517 $revUser = $revRecord->getUser(
1518 $isPublic ? RevisionRecord::FOR_PUBLIC : RevisionRecord::FOR_THIS_USER,
1519 $authority
1520 );
1521 if ( $revUser ) {
1522 $link = self::userLink(
1523 $revUser->getId(),
1524 $revUser->getName(),
1525 false,
1526 [ 'data-mw-revid' => $revRecord->getId() ]
1527 ) . self::userToolLinks(
1528 $revUser->getId(),
1529 $revUser->getName(),
1530 false,
1531 0,
1532 null,
1533 $useParentheses
1534 );
1535 } else {
1536 // User is deleted and we can't (or don't want to) view it
1537 $link = wfMessage( 'rev-deleted-user' )->escaped();
1538 }
1539
1540 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
1541 $class = self::getRevisionDeletedClass( $revRecord );
1542 return ' <span class="' . $class . ' mw-userlink">' . $link . '</span>';
1543 }
1544 return $link;
1545 }
1546
1557 public static function expandLocalLinks( string $html ) {
1558 return HtmlHelper::modifyElements(
1559 $html,
1560 static function ( SerializerNode $node ): bool {
1561 return $node->name === 'a' && isset( $node->attrs['href'] );
1562 },
1563 static function ( SerializerNode $node ): SerializerNode {
1564 $node->attrs['href'] =
1565 wfExpandUrl( $node->attrs['href'], PROTO_RELATIVE );
1566 return $node;
1567 }
1568 );
1569 }
1570
1591 public static function formatComment(
1592 $comment, $title = null, $local = false, $wikiId = null
1593 ) {
1594 wfDeprecated( __METHOD__, '1.41' );
1595 $formatter = MediaWikiServices::getInstance()->getCommentFormatter();
1596 return $formatter->format( $comment, $title, $local, $wikiId );
1597 }
1598
1618 public static function formatLinksInComment(
1619 $comment, $title = null, $local = false, $wikiId = null
1620 ) {
1621 wfDeprecated( __METHOD__, '1.41' );
1622 $formatter = MediaWikiServices::getInstance()->getCommentFormatter();
1623 return $formatter->formatLinksUnsafe( $comment, $title, $local, $wikiId );
1624 }
1625
1632 public static function normalizeSubpageLink( $contextTitle, $target, &$text ) {
1633 # Valid link forms:
1634 # Foobar -- normal
1635 # :Foobar -- override special treatment of prefix (images, language links)
1636 # /Foobar -- convert to CurrentPage/Foobar
1637 # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial and final / from text
1638 # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage
1639 # ../Foobar -- convert to CurrentPage/Foobar,
1640 # (from CurrentPage/CurrentSubPage)
1641 # ../Foobar/ -- convert to CurrentPage/Foobar, use 'Foobar' as text
1642 # (from CurrentPage/CurrentSubPage)
1643
1644 $ret = $target; # default return value is no change
1645
1646 # Some namespaces don't allow subpages,
1647 # so only perform processing if subpages are allowed
1648 if (
1649 $contextTitle && MediaWikiServices::getInstance()->getNamespaceInfo()->
1650 hasSubpages( $contextTitle->getNamespace() )
1651 ) {
1652 $hash = strpos( $target, '#' );
1653 if ( $hash !== false ) {
1654 $suffix = substr( $target, $hash );
1655 $target = substr( $target, 0, $hash );
1656 } else {
1657 $suffix = '';
1658 }
1659 # T9425
1660 $target = trim( $target );
1661 $contextPrefixedText = MediaWikiServices::getInstance()->getTitleFormatter()->
1662 getPrefixedText( $contextTitle );
1663 # Look at the first character
1664 if ( $target != '' && $target[0] === '/' ) {
1665 # / at end means we don't want the slash to be shown
1666 $m = [];
1667 $trailingSlashes = preg_match_all( '%(/+)$%', $target, $m );
1668 if ( $trailingSlashes ) {
1669 $noslash = $target = substr( $target, 1, -strlen( $m[0][0] ) );
1670 } else {
1671 $noslash = substr( $target, 1 );
1672 }
1673
1674 $ret = $contextPrefixedText . '/' . trim( $noslash ) . $suffix;
1675 if ( $text === '' ) {
1676 $text = $target . $suffix;
1677 } # this might be changed for ugliness reasons
1678 } else {
1679 # check for .. subpage backlinks
1680 $dotdotcount = 0;
1681 $nodotdot = $target;
1682 while ( str_starts_with( $nodotdot, '../' ) ) {
1683 ++$dotdotcount;
1684 $nodotdot = substr( $nodotdot, 3 );
1685 }
1686 if ( $dotdotcount > 0 ) {
1687 $exploded = explode( '/', $contextPrefixedText );
1688 if ( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page
1689 $ret = implode( '/', array_slice( $exploded, 0, -$dotdotcount ) );
1690 # / at the end means don't show full path
1691 if ( substr( $nodotdot, -1, 1 ) === '/' ) {
1692 $nodotdot = rtrim( $nodotdot, '/' );
1693 if ( $text === '' ) {
1694 $text = $nodotdot . $suffix;
1695 }
1696 }
1697 $nodotdot = trim( $nodotdot );
1698 if ( $nodotdot != '' ) {
1699 $ret .= '/' . $nodotdot;
1700 }
1701 $ret .= $suffix;
1702 }
1703 }
1704 }
1705 }
1706
1707 return $ret;
1708 }
1709
1729 public static function commentBlock(
1730 $comment, $title = null, $local = false, $wikiId = null, $useParentheses = true
1731 ) {
1732 wfDeprecated( __METHOD__, '1.41' );
1733 return MediaWikiServices::getInstance()->getCommentFormatter()
1734 ->formatBlock( $comment, $title, $local, $wikiId, $useParentheses );
1735 }
1736
1752 public static function revComment(
1753 RevisionRecord $revRecord,
1754 $local = false,
1755 $isPublic = false,
1756 $useParentheses = true
1757 ) {
1758 wfDeprecated( __METHOD__, '1.41' );
1759 $authority = RequestContext::getMain()->getAuthority();
1760 $formatter = MediaWikiServices::getInstance()->getCommentFormatter();
1761 return $formatter->formatRevision( $revRecord, $authority, $local, $isPublic, $useParentheses );
1762 }
1763
1769 public static function formatRevisionSize( $size ) {
1770 if ( $size == 0 ) {
1771 $stxt = wfMessage( 'historyempty' )->escaped();
1772 } else {
1773 $stxt = wfMessage( 'nbytes' )->numParams( $size )->escaped();
1774 }
1775 return "<span class=\"history-size mw-diff-bytes\" data-mw-bytes=\"$size\">$stxt</span>";
1776 }
1777
1784 public static function tocIndent() {
1785 return "\n<ul>\n";
1786 }
1787
1795 public static function tocUnindent( $level ) {
1796 return "</li>\n" . str_repeat( "</ul>\n</li>\n", $level > 0 ? $level : 0 );
1797 }
1798
1810 public static function tocLine( $linkAnchor, $tocline, $tocnumber, $level, $sectionIndex = false ) {
1811 $classes = "toclevel-$level";
1812
1813 // Parser.php used to suppress tocLine by setting $sectionindex to false.
1814 // In those circumstances, we can now encounter '' or a "T-" prefixed index
1815 // for when the section comes from templates.
1816 if ( $sectionIndex !== false && $sectionIndex !== '' && !str_starts_with( $sectionIndex, "T-" ) ) {
1817 $classes .= " tocsection-$sectionIndex";
1818 }
1819
1820 // <li class="$classes"><a href="#$linkAnchor"><span class="tocnumber">
1821 // $tocnumber</span> <span class="toctext">$tocline</span></a>
1822 return Html::openElement( 'li', [ 'class' => $classes ] )
1823 . Html::rawElement( 'a',
1824 [ 'href' => "#$linkAnchor" ],
1825 Html::element( 'span', [ 'class' => 'tocnumber' ], $tocnumber )
1826 . ' '
1827 . Html::rawElement( 'span', [ 'class' => 'toctext' ], $tocline )
1828 );
1829 }
1830
1838 public static function tocLineEnd() {
1839 return "</li>\n";
1840 }
1841
1850 public static function tocList( $toc, Language $lang = null ) {
1851 $lang ??= RequestContext::getMain()->getLanguage();
1852
1853 $title = wfMessage( 'toc' )->inLanguage( $lang )->escaped();
1854
1855 return '<div id="toc" class="toc" role="navigation" aria-labelledby="mw-toc-heading">'
1856 . Html::element( 'input', [
1857 'type' => 'checkbox',
1858 'role' => 'button',
1859 'id' => 'toctogglecheckbox',
1860 'class' => 'toctogglecheckbox',
1861 'style' => 'display:none',
1862 ] )
1863 . Html::openElement( 'div', [
1864 'class' => 'toctitle',
1865 'lang' => $lang->getHtmlCode(),
1866 'dir' => $lang->getDir(),
1867 ] )
1868 . '<h2 id="mw-toc-heading">' . $title . '</h2>'
1869 . '<span class="toctogglespan">'
1870 . Html::label( '', 'toctogglecheckbox', [
1871 'class' => 'toctogglelabel',
1872 ] )
1873 . '</span>'
1874 . '</div>'
1875 . $toc
1876 . "</ul>\n</div>\n";
1877 }
1878
1890 public static function generateTOC( ?TOCData $tocData, Language $lang = null, array $options = [] ): string {
1891 $toc = '';
1892 $lastLevel = 0;
1893 $maxTocLevel = $options['maxtoclevel'] ?? null;
1894 if ( $maxTocLevel === null ) {
1895 // Use wiki-configured default
1896 $services = MediaWikiServices::getInstance();
1897 $config = $services->getMainConfig();
1898 $maxTocLevel = $config->get( MainConfigNames::MaxTocLevel );
1899 }
1900 foreach ( ( $tocData ? $tocData->getSections() : [] ) as $section ) {
1901 $tocLevel = $section->tocLevel;
1902 if ( $tocLevel < $maxTocLevel ) {
1903 if ( $tocLevel > $lastLevel ) {
1904 $toc .= self::tocIndent();
1905 } elseif ( $tocLevel < $lastLevel ) {
1906 if ( $lastLevel < $maxTocLevel ) {
1907 $toc .= self::tocUnindent(
1908 $lastLevel - $tocLevel );
1909 } else {
1910 $toc .= self::tocLineEnd();
1911 }
1912 } else {
1913 $toc .= self::tocLineEnd();
1914 }
1915
1916 $toc .= self::tocLine( $section->linkAnchor,
1917 $section->line, $section->number,
1918 $tocLevel, $section->index );
1919 $lastLevel = $tocLevel;
1920 }
1921 }
1922 if ( $lastLevel < $maxTocLevel && $lastLevel > 0 ) {
1923 $toc .= self::tocUnindent( $lastLevel - 1 );
1924 }
1925 return self::tocList( $toc, $lang );
1926 }
1927
1944 public static function makeHeadline( $level, $attribs, $anchor, $html,
1945 $link, $fallbackAnchor = false
1946 ) {
1947 $anchorEscaped = htmlspecialchars( $anchor, ENT_COMPAT );
1948 $fallback = '';
1949 if ( $fallbackAnchor !== false && $fallbackAnchor !== $anchor ) {
1950 $fallbackAnchor = htmlspecialchars( $fallbackAnchor, ENT_COMPAT );
1951 $fallback = "<span id=\"$fallbackAnchor\"></span>";
1952 }
1953 return "<h$level$attribs"
1954 . "$fallback<span class=\"mw-headline\" id=\"$anchorEscaped\">$html</span>"
1955 . $link
1956 . "</h$level>";
1957 }
1958
1965 public static function splitTrail( $trail ) {
1966 $regex = MediaWikiServices::getInstance()->getContentLanguage()->linkTrail();
1967 $inside = '';
1968 if ( $trail !== '' && preg_match( $regex, $trail, $m ) ) {
1969 [ , $inside, $trail ] = $m;
1970 }
1971 return [ $inside, $trail ];
1972 }
1973
2004 public static function generateRollback(
2005 RevisionRecord $revRecord,
2006 IContextSource $context = null,
2007 $options = []
2008 ) {
2009 $context ??= RequestContext::getMain();
2010
2011 $editCount = self::getRollbackEditCount( $revRecord );
2012 if ( $editCount === false ) {
2013 return '';
2014 }
2015
2016 $inner = self::buildRollbackLink( $revRecord, $context, $editCount );
2017
2018 $services = MediaWikiServices::getInstance();
2019 // Allow extensions to modify the rollback link.
2020 // Abort further execution if the extension wants full control over the link.
2021 if ( !( new HookRunner( $services->getHookContainer() ) )->onLinkerGenerateRollbackLink(
2022 $revRecord, $context, $options, $inner ) ) {
2023 return $inner;
2024 }
2025
2026 if ( !in_array( 'noBrackets', $options, true ) ) {
2027 $inner = $context->msg( 'brackets' )->rawParams( $inner )->escaped();
2028 }
2029
2030 if ( $services->getUserOptionsLookup()
2031 ->getBoolOption( $context->getUser(), 'showrollbackconfirmation' )
2032 ) {
2033 $stats = $services->getStatsdDataFactory();
2034 $stats->increment( 'rollbackconfirmation.event.load' );
2035 $context->getOutput()->addModules( 'mediawiki.misc-authed-curate' );
2036 }
2037
2038 return '<span class="mw-rollback-link">' . $inner . '</span>';
2039 }
2040
2059 public static function getRollbackEditCount( RevisionRecord $revRecord, $verify = true ) {
2060 if ( func_num_args() > 1 ) {
2061 wfDeprecated( __METHOD__ . ' with $verify parameter', '1.40' );
2062 }
2063 $showRollbackEditCount = MediaWikiServices::getInstance()->getMainConfig()
2064 ->get( MainConfigNames::ShowRollbackEditCount );
2065
2066 if ( !is_int( $showRollbackEditCount ) || !$showRollbackEditCount > 0 ) {
2067 // Nothing has happened, indicate this by returning 'null'
2068 return null;
2069 }
2070
2071 $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
2072
2073 // Up to the value of $wgShowRollbackEditCount revisions are counted
2074 $queryBuilder = MediaWikiServices::getInstance()->getRevisionStore()->newSelectQueryBuilder( $dbr );
2075 $res = $queryBuilder->where( [ 'rev_page' => $revRecord->getPageId() ] )
2076 ->useIndex( [ 'revision' => 'rev_page_timestamp' ] )
2077 ->orderBy( [ 'rev_timestamp', 'rev_id' ], SelectQueryBuilder::SORT_DESC )
2078 ->limit( $showRollbackEditCount + 1 )
2079 ->caller( __METHOD__ )->fetchResultSet();
2080
2081 $revUser = $revRecord->getUser( RevisionRecord::RAW );
2082 $revUserText = $revUser ? $revUser->getName() : '';
2083
2084 $editCount = 0;
2085 $moreRevs = false;
2086 foreach ( $res as $row ) {
2087 if ( $row->rev_user_text != $revUserText ) {
2088 if ( $row->rev_deleted & RevisionRecord::DELETED_TEXT
2089 || $row->rev_deleted & RevisionRecord::DELETED_USER
2090 ) {
2091 // If the user or the text of the revision we might rollback
2092 // to is deleted in some way we can't rollback. Similar to
2093 // the checks in WikiPage::commitRollback.
2094 return false;
2095 }
2096 $moreRevs = true;
2097 break;
2098 }
2099 $editCount++;
2100 }
2101
2102 if ( $editCount <= $showRollbackEditCount && !$moreRevs ) {
2103 // We didn't find at least $wgShowRollbackEditCount revisions made by the current user
2104 // and there weren't any other revisions. That means that the current user is the only
2105 // editor, so we can't rollback
2106 return false;
2107 }
2108 return $editCount;
2109 }
2110
2125 public static function buildRollbackLink(
2126 RevisionRecord $revRecord,
2127 IContextSource $context = null,
2128 $editCount = false
2129 ) {
2130 $config = MediaWikiServices::getInstance()->getMainConfig();
2131 $showRollbackEditCount = $config->get( MainConfigNames::ShowRollbackEditCount );
2132 $miserMode = $config->get( MainConfigNames::MiserMode );
2133 // To config which pages are affected by miser mode
2134 $disableRollbackEditCountSpecialPage = [ 'Recentchanges', 'Watchlist' ];
2135
2136 $context ??= RequestContext::getMain();
2137
2138 $title = $revRecord->getPageAsLinkTarget();
2139 $revUser = $revRecord->getUser();
2140 $revUserText = $revUser ? $revUser->getName() : '';
2141
2142 $query = [
2143 'action' => 'rollback',
2144 'from' => $revUserText,
2145 'token' => $context->getUser()->getEditToken( 'rollback' ),
2146 ];
2147
2148 $attrs = [
2149 'data-mw' => 'interface',
2150 'title' => $context->msg( 'tooltip-rollback' )->text()
2151 ];
2152
2153 $options = [ 'known', 'noclasses' ];
2154
2155 if ( $context->getRequest()->getBool( 'bot' ) ) {
2156 // T17999
2157 $query['hidediff'] = '1';
2158 $query['bot'] = '1';
2159 }
2160
2161 if ( $miserMode ) {
2162 foreach ( $disableRollbackEditCountSpecialPage as $specialPage ) {
2163 if ( $context->getTitle()->isSpecial( $specialPage ) ) {
2164 $showRollbackEditCount = false;
2165 break;
2166 }
2167 }
2168 }
2169
2170 // The edit count can be 0 on replica lag, fall back to the generic rollbacklink message
2171 $msg = [ 'rollbacklink' ];
2172 if ( is_int( $showRollbackEditCount ) && $showRollbackEditCount > 0 ) {
2173 if ( !is_numeric( $editCount ) ) {
2174 $editCount = self::getRollbackEditCount( $revRecord );
2175 }
2176
2177 if ( $editCount > $showRollbackEditCount ) {
2178 $msg = [ 'rollbacklinkcount-morethan', Message::numParam( $showRollbackEditCount ) ];
2179 } elseif ( $editCount ) {
2180 $msg = [ 'rollbacklinkcount', Message::numParam( $editCount ) ];
2181 }
2182 }
2183
2184 $html = $context->msg( ...$msg )->parse();
2185 return self::link( $title, $html, $attrs, $query, $options );
2186 }
2187
2196 public static function formatHiddenCategories( $hiddencats ) {
2197 $outText = '';
2198 if ( count( $hiddencats ) > 0 ) {
2199 # Construct the HTML
2200 $outText = '<div class="mw-hiddenCategoriesExplanation">';
2201 $outText .= wfMessage( 'hiddencategories' )->numParams( count( $hiddencats ) )->parseAsBlock();
2202 $outText .= "</div><ul>\n";
2203
2204 foreach ( $hiddencats as $titleObj ) {
2205 # If it's hidden, it must exist - no need to check with a LinkBatch
2206 $outText .= '<li>'
2207 . self::link( $titleObj, null, [], [], 'known' )
2208 . "</li>\n";
2209 }
2210 $outText .= '</ul>';
2211 }
2212 return $outText;
2213 }
2214
2218 private static function getContextFromMain() {
2219 $context = RequestContext::getMain();
2220 $context = new DerivativeContext( $context );
2221 return $context;
2222 }
2223
2241 public static function titleAttrib( $name, $options = null, array $msgParams = [], $localizer = null ) {
2242 if ( !$localizer ) {
2243 $localizer = self::getContextFromMain();
2244 }
2245 $message = $localizer->msg( "tooltip-$name", $msgParams );
2246 // Set a default tooltip for subject namespace tabs if that hasn't
2247 // been defined. See T22126
2248 if ( !$message->exists() && str_starts_with( $name, 'ca-nstab-' ) ) {
2249 $message = $localizer->msg( 'tooltip-ca-nstab' );
2250 }
2251
2252 if ( $message->isDisabled() ) {
2253 $tooltip = false;
2254 } else {
2255 $tooltip = $message->text();
2256 # Compatibility: formerly some tooltips had [alt-.] hardcoded
2257 $tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip );
2258 }
2259
2260 $options = (array)$options;
2261
2262 if ( in_array( 'nonexisting', $options ) ) {
2263 $tooltip = $localizer->msg( 'red-link-title', $tooltip ?: '' )->text();
2264 }
2265 if ( in_array( 'withaccess', $options ) ) {
2266 $accesskey = self::accesskey( $name, $localizer );
2267 if ( $accesskey !== false ) {
2268 // Should be build the same as in jquery.accessKeyLabel.js
2269 if ( $tooltip === false || $tooltip === '' ) {
2270 $tooltip = $localizer->msg( 'brackets', $accesskey )->text();
2271 } else {
2272 $tooltip .= $localizer->msg( 'word-separator' )->text();
2273 $tooltip .= $localizer->msg( 'brackets', $accesskey )->text();
2274 }
2275 }
2276 }
2277
2278 return $tooltip;
2279 }
2280
2281 public static $accesskeycache;
2282
2295 public static function accesskey( $name, $localizer = null ) {
2296 if ( !isset( self::$accesskeycache[$name] ) ) {
2297 if ( !$localizer ) {
2298 $localizer = self::getContextFromMain();
2299 }
2300 $msg = $localizer->msg( "accesskey-$name" );
2301 // Set a default accesskey for subject namespace tabs if an
2302 // accesskey has not been defined. See T22126
2303 if ( !$msg->exists() && str_starts_with( $name, 'ca-nstab-' ) ) {
2304 $msg = $localizer->msg( 'accesskey-ca-nstab' );
2305 }
2306 self::$accesskeycache[$name] = $msg->isDisabled() ? false : $msg->plain();
2307 }
2308 return self::$accesskeycache[$name];
2309 }
2310
2325 public static function getRevDeleteLink(
2326 Authority $performer,
2327 RevisionRecord $revRecord,
2328 LinkTarget $title
2329 ) {
2330 $canHide = $performer->isAllowed( 'deleterevision' );
2331 $canHideHistory = $performer->isAllowed( 'deletedhistory' );
2332 if ( !$canHide && !( $revRecord->getVisibility() && $canHideHistory ) ) {
2333 return '';
2334 }
2335
2336 if ( !$revRecord->userCan( RevisionRecord::DELETED_RESTRICTED, $performer ) ) {
2337 return self::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
2338 }
2339 $prefixedDbKey = MediaWikiServices::getInstance()->getTitleFormatter()->
2340 getPrefixedDBkey( $title );
2341 if ( $revRecord->getId() ) {
2342 // RevDelete links using revision ID are stable across
2343 // page deletion and undeletion; use when possible.
2344 $query = [
2345 'type' => 'revision',
2346 'target' => $prefixedDbKey,
2347 'ids' => $revRecord->getId()
2348 ];
2349 } else {
2350 // Older deleted entries didn't save a revision ID.
2351 // We have to refer to these by timestamp, ick!
2352 $query = [
2353 'type' => 'archive',
2354 'target' => $prefixedDbKey,
2355 'ids' => $revRecord->getTimestamp()
2356 ];
2357 }
2358 return self::revDeleteLink(
2359 $query,
2360 $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ),
2361 $canHide
2362 );
2363 }
2364
2377 public static function revDeleteLink( $query = [], $restricted = false, $delete = true ) {
2378 $sp = SpecialPage::getTitleFor( 'Revisiondelete' );
2379 $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
2380 $html = wfMessage( $msgKey )->escaped();
2381 $tag = $restricted ? 'strong' : 'span';
2382 $link = self::link( $sp, $html, [], $query, [ 'known', 'noclasses' ] );
2383 return Xml::tags(
2384 $tag,
2385 [ 'class' => 'mw-revdelundel-link' ],
2386 wfMessage( 'parentheses' )->rawParams( $link )->escaped()
2387 );
2388 }
2389
2401 public static function revDeleteLinkDisabled( $delete = true ) {
2402 $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
2403 $html = wfMessage( $msgKey )->escaped();
2404 $htmlParentheses = wfMessage( 'parentheses' )->rawParams( $html )->escaped();
2405 return Xml::tags( 'span', [ 'class' => 'mw-revdelundel-link' ], $htmlParentheses );
2406 }
2407
2421 public static function tooltipAndAccesskeyAttribs(
2422 $name,
2423 array $msgParams = [],
2424 $options = null,
2425 $localizer = null
2426 ) {
2427 $options = (array)$options;
2428 $options[] = 'withaccess';
2429
2430 // Get optional parameters from global context if any missing.
2431 if ( !$localizer ) {
2432 $localizer = self::getContextFromMain();
2433 }
2434
2435 $attribs = [
2436 'title' => self::titleAttrib( $name, $options, $msgParams, $localizer ),
2437 'accesskey' => self::accesskey( $name, $localizer )
2438 ];
2439 if ( $attribs['title'] === false ) {
2440 unset( $attribs['title'] );
2441 }
2442 if ( $attribs['accesskey'] === false ) {
2443 unset( $attribs['accesskey'] );
2444 }
2445 return $attribs;
2446 }
2447
2455 public static function tooltip( $name, $options = null ) {
2456 $tooltip = self::titleAttrib( $name, $options );
2457 if ( $tooltip === false ) {
2458 return '';
2459 }
2460 return Xml::expandAttributes( [
2461 'title' => $tooltip
2462 ] );
2463 }
2464
2465}
2466
2470class_alias( Linker::class, 'Linker' );
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:202
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.
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL using $wgServer (or one of its alternatives).
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
wfCgiToArray( $query)
This is the logical opposite of wfArrayToCgi(): it accepts a query string as its argument and returns...
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
$fallback
if(!defined( 'MW_NO_SESSION') &&MW_ENTRY_POINT !=='cli') $wgLang
Definition Setup.php:536
if(!defined( 'MW_NO_SESSION') &&MW_ENTRY_POINT !=='cli') $wgTitle
Definition Setup.php:536
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition File.php:73
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:30
Base class for language-specific code.
Definition Language.php:63
Basic media transform error class.
Base class for the output of MediaHandler::doTransform() and File::transform().
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
An IContextSource implementation which will inherit context from another source but allow individual ...
Group all the pieces relevant to the context of a request into one instance.
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Static utilities for manipulating HTML strings.
This class is a collection of static functions that serve two purposes:
Definition Html.php:56
Some internal bits split of from Skin.php.
Definition Linker.php:65
static expandLocalLinks(string $html)
Helper function to expand local links.
Definition Linker.php:1557
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:1752
static revDeleteLink( $query=[], $restricted=false, $delete=true)
Creates a (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2377
static link( $target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition Linker.php:113
static tocLineEnd()
End a Table Of Contents line.
Definition Linker.php:1838
static blockLink( $userId, $userText)
Definition Linker.php:1414
static makeSelfLinkObj( $nt, $html='', $query='', $trail='', $prefix='', $hash='')
Make appropriate markup for a link to the current article.
Definition Linker.php:195
static tooltipAndAccesskeyAttribs( $name, array $msgParams=[], $options=null, $localizer=null)
Returns the attributes for the tooltip and access key.
Definition Linker.php:2421
static getUploadUrl( $destFile, $query='')
Get the URL to upload a certain file.
Definition Linker.php:1007
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:329
static makeMediaLinkObj( $title, $html='', $time=false)
Create a direct link to a given uploaded file.
Definition Linker.php:1038
static processResponsiveImages( $file, $thumb, $hp)
Process responsive images: add 1.5x and 2x subimages to the thumbnail, where applicable.
Definition Linker.php:897
static userTalkLink( $userId, $userText)
Definition Linker.php:1392
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:1729
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:1591
static normalizeSubpageLink( $contextTitle, $target, &$text)
Definition Linker.php:1632
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:1099
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:1357
static emailLink( $userId, $userText)
Definition Linker.php:1435
static formatHiddenCategories( $hiddencats)
Returns HTML for the "hidden categories on this page" list.
Definition Linker.php:2196
static getInvalidTitleDescription(IContextSource $context, $namespace, $title)
Get a message saying that an invalid title was encountered.
Definition Linker.php:228
static getRollbackEditCount(RevisionRecord $revRecord, $verify=true)
This function will return the number of revisions which a rollback would revert and will verify that ...
Definition Linker.php:2059
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition Linker.php:1146
static userToolLinkArray( $userId, $userText, $redContribsWhenNoEdits=false, $flags=0, $edits=null)
Generate standard user tool links (talk, contributions, block link, etc.)
Definition Linker.php:1262
static getImageLinkMTOParams( $frameParams, $query='', $parser=null)
Get the link parameters for MediaTransformOutput::toHtml() from given frame parameters supplied by th...
Definition Linker.php:584
static generateTOC(?TOCData $tocData, Language $lang=null, array $options=[])
Definition Linker.php:1890
static linkKnown( $target, $html=null, $customAttribs=[], $query=[], $options=[ 'known'])
Identical to link(), except $options defaults to 'known'.
Definition Linker.php:171
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:2325
static makeThumbLink2(LinkTarget $title, $file, $frameParams=[], $handlerParams=[], $time=false, $query='', array $classes=[], ?Parser $parser=null)
Definition Linker.php:660
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:1850
static makeHeadline( $level, $attribs, $anchor, $html, $link, $fallbackAnchor=false)
Create a headline for content.
Definition Linker.php:1944
static makeExternalImage( $url, $alt='')
Return the code for images which were added via external links, via Parser::maybeMakeExternalImage().
Definition Linker.php:271
static tooltip( $name, $options=null)
Returns raw bits of HTML, use titleAttrib()
Definition Linker.php:2455
static makeBrokenImageLinkObj( $title, $label='', $query='', $unused1='', $unused2='', $time=false, array $handlerParams=[], bool $currentExists=false)
Make a "broken" link to an image.
Definition Linker.php:934
static makeMediaLinkFile(LinkTarget $title, $file, $html='')
Create a direct link to a given uploaded file.
Definition Linker.php:1057
static accesskey( $name, $localizer=null)
Given the id of an interface element, constructs the appropriate accesskey attribute from the system ...
Definition Linker.php:2295
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:2241
static renderUserToolLinksArray(array $items, bool $useParentheses)
Generate standard tool links HTML from a link array returned by userToolLinkArray().
Definition Linker.php:1321
static userToolLinksRedContribs( $userId, $userText, $edits=null, $useParentheses=true)
Alias for userToolLinks( $userId, $userText, true );.
Definition Linker.php:1380
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:1965
static tocIndent()
Add another level to the Table of Contents.
Definition Linker.php:1784
const TOOL_LINKS_NOBLOCK
Flags for userToolLinks()
Definition Linker.php:69
static revDeleteLinkDisabled( $delete=true)
Creates a dead (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2401
static formatRevisionSize( $size)
Definition Linker.php:1769
static tocUnindent( $level)
Finish one or more sublevels on the Table of Contents.
Definition Linker.php:1795
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:1618
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:627
static userLink( $userId, $userName, $altUserName=false, $attributes=[])
Make user link (or user contributions for unregistered users)
Definition Linker.php:1200
static revUserLink(RevisionRecord $revRecord, $isPublic=false)
Generate a user link if the current user is allowed to view it.
Definition Linker.php:1461
static getRevisionDeletedClass(RevisionRecord $revisionRecord)
Returns css class of a deleted revision.
Definition Linker.php:1489
static generateRollback(RevisionRecord $revRecord, IContextSource $context=null, $options=[])
Generate a rollback link for a given revision.
Definition Linker.php:2004
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:1509
static tocLine( $linkAnchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition Linker.php:1810
static buildRollbackLink(RevisionRecord $revRecord, IContextSource $context=null, $editCount=false)
Build a raw rollback link, useful for collections of "tool" links.
Definition Linker.php:2125
A class containing constants representing the names of configuration variables.
const UploadNavigationUrl
Name constant for the UploadNavigationUrl setting, for use with Config::get()
const ThumbUpright
Name constant for the ThumbUpright setting, for use with Config::get()
const EnableUploads
Name constant for the EnableUploads setting, for use with Config::get()
const SVGMaxSize
Name constant for the SVGMaxSize setting, for use with Config::get()
const ResponsiveImages
Name constant for the ResponsiveImages setting, for use with Config::get()
const DisableAnonTalk
Name constant for the DisableAnonTalk setting, for use with Config::get()
const ParserEnableLegacyMediaDOM
Name constant for the ParserEnableLegacyMediaDOM setting, for use with Config::get()
const ThumbLimits
Name constant for the ThumbLimits setting, for use with Config::get()
const UploadMissingFileUrl
Name constant for the UploadMissingFileUrl setting, for use with Config::get()
Service locator for MediaWiki core services.
getMainConfig()
Returns the Config object that provides configuration for MediaWiki core.
static getInstance()
Returns the global default instance of the top level service locator.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:157
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition Parser.php:155
getBadFileLookup()
Get the BadFileLookup instance that this Parser is using.
Definition Parser.php:1232
getTargetLanguage()
Get the target language for the content being parsed.
Definition Parser.php:1158
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.
Parent class for all special pages.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
static getTitleValueFor( $name, $subpage=false, $fragment='')
Get a localised TitleValue object for a specified special page name.
Represents the target of a wiki link.
Represents a title within MediaWiki.
Definition Title.php:78
Class to parse and build external user names.
Value object representing a user's identity.
Build SELECT queries with a fluent interface.
Module of static functions for generating XML.
Definition Xml.php:33
Interface for objects which can provide a MediaWiki context on request.
Represents the target of a wiki link.
getDBkey()
Get the main part of the link target, in canonical database form.
getText()
Get the main part of the link target, in text form.
This interface represents the authority associated with the current execution context,...
Definition Authority.php:37
isAllowed(string $permission, PermissionStatus $status=null)
Checks whether this authority has the given permission in general.
Interface for localizing messages in MediaWiki.
msg( $key,... $params)
This is the method for getting translated interface messages.
element(SerializerNode $parent, SerializerNode $node, $contents)