MediaWiki 1.42.1
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
1145 public static function makeExternalLink( $url, $text, $escape = true,
1146 $linktype = '', $attribs = [], $title = null
1147 ) {
1148 global $wgTitle;
1149 $class = 'external';
1150 if ( $linktype ) {
1151 $class .= " $linktype";
1152 }
1153 if ( isset( $attribs['class'] ) && $attribs['class'] ) {
1154 $class .= " {$attribs['class']}";
1155 }
1156 $attribs['class'] = $class;
1157
1158 if ( $escape ) {
1159 $text = htmlspecialchars( $text, ENT_COMPAT );
1160 }
1161
1162 if ( !$title ) {
1163 $title = $wgTitle;
1164 }
1165 $newRel = Parser::getExternalLinkRel( $url, $title );
1166 if ( !isset( $attribs['rel'] ) || $attribs['rel'] === '' ) {
1167 $attribs['rel'] = $newRel;
1168 } elseif ( $newRel !== null ) {
1169 // Merge the rel attributes.
1170 $newRels = explode( ' ', $newRel );
1171 $oldRels = explode( ' ', $attribs['rel'] );
1172 $combined = array_unique( array_merge( $newRels, $oldRels ) );
1173 $attribs['rel'] = implode( ' ', $combined );
1174 }
1175 $link = '';
1176 $success = ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )->onLinkerMakeExternalLink(
1177 $url, $text, $link, $attribs, $linktype );
1178 if ( !$success ) {
1179 wfDebug( "Hook LinkerMakeExternalLink changed the output of link "
1180 . "with url {$url} and text {$text} to {$link}" );
1181 return $link;
1182 }
1183 $attribs['href'] = $url;
1184 return Html::rawElement( 'a', $attribs, $text );
1185 }
1186
1199 public static function userLink(
1200 $userId,
1201 $userName,
1202 $altUserName = false,
1203 $attributes = []
1204 ) {
1205 if ( $userName === '' || $userName === false || $userName === null ) {
1206 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1207 'that need to be fixed?' );
1208 return wfMessage( 'empty-username' )->parse();
1209 }
1210
1211 $classes = 'mw-userlink';
1212 if ( MediaWikiServices::getInstance()->getTempUserConfig()->isTempName( $userName ) ) {
1213 $classes .= ' mw-tempuserlink';
1214 $page = SpecialPage::getTitleValueFor( 'Contributions', $userName );
1215 } elseif ( $userId == 0 ) {
1216 $page = ExternalUserNames::getUserLinkTitle( $userName );
1217
1218 if ( ExternalUserNames::isExternal( $userName ) ) {
1219 $classes .= ' mw-extuserlink';
1220 } elseif ( $altUserName === false ) {
1221 $altUserName = IPUtils::prettifyIP( $userName );
1222 }
1223 $classes .= ' mw-anonuserlink'; // Separate link class for anons (T45179)
1224 } else {
1225 $page = TitleValue::tryNew( NS_USER, strtr( $userName, ' ', '_' ) );
1226 }
1227
1228 // Wrap the output with <bdi> tags for directionality isolation
1229 $linkText =
1230 '<bdi>' . htmlspecialchars( $altUserName !== false ? $altUserName : $userName ) . '</bdi>';
1231
1232 if ( isset( $attributes['class'] ) ) {
1233 $attributes['class'] .= ' ' . $classes;
1234 } else {
1235 $attributes['class'] = $classes;
1236 }
1237
1238 return $page
1239 ? self::link( $page, $linkText, $attributes )
1240 : Html::rawElement( 'span', $attributes, $linkText );
1241 }
1242
1261 public static function userToolLinkArray(
1262 $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits = null
1263 ): array {
1264 $services = MediaWikiServices::getInstance();
1265 $disableAnonTalk = $services->getMainConfig()->get( MainConfigNames::DisableAnonTalk );
1266 $talkable = !( $disableAnonTalk && $userId == 0 );
1267 $blockable = !( $flags & self::TOOL_LINKS_NOBLOCK );
1268 $addEmailLink = $flags & self::TOOL_LINKS_EMAIL && $userId;
1269
1270 if ( $userId == 0 && ExternalUserNames::isExternal( $userText ) ) {
1271 // No tools for an external user
1272 return [];
1273 }
1274
1275 $items = [];
1276 if ( $talkable ) {
1277 $items[] = self::userTalkLink( $userId, $userText );
1278 }
1279 if ( $userId ) {
1280 // check if the user has an edit
1281 $attribs = [];
1282 $attribs['class'] = 'mw-usertoollinks-contribs';
1283 if ( $redContribsWhenNoEdits ) {
1284 if ( $edits === null ) {
1285 $user = UserIdentityValue::newRegistered( $userId, $userText );
1286 $edits = $services->getUserEditTracker()->getUserEditCount( $user );
1287 }
1288 if ( $edits === 0 ) {
1289 // Note: "new" class is inappropriate here, as "new" class
1290 // should only be used for pages that do not exist.
1291 $attribs['class'] .= ' mw-usertoollinks-contribs-no-edits';
1292 }
1293 }
1294 $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText );
1295
1296 $items[] = self::link( $contribsPage, wfMessage( 'contribslink' )->escaped(), $attribs );
1297 }
1298 $userCanBlock = RequestContext::getMain()->getAuthority()->isAllowed( 'block' );
1299 if ( $blockable && $userCanBlock ) {
1300 $items[] = self::blockLink( $userId, $userText );
1301 }
1302
1303 $user = RequestContext::getMain()->getUser();
1304 if ( $addEmailLink && $user->canSendEmail() ) {
1305 $items[] = self::emailLink( $userId, $userText );
1306 }
1307
1308 ( new HookRunner( $services->getHookContainer() ) )->onUserToolLinksEdit( $userId, $userText, $items );
1309
1310 return $items;
1311 }
1312
1320 public static function renderUserToolLinksArray( array $items, bool $useParentheses ): string {
1321 global $wgLang;
1322
1323 if ( !$items ) {
1324 return '';
1325 }
1326
1327 if ( $useParentheses ) {
1328 return wfMessage( 'word-separator' )->escaped()
1329 . '<span class="mw-usertoollinks">'
1330 . wfMessage( 'parentheses' )->rawParams( $wgLang->pipeList( $items ) )->escaped()
1331 . '</span>';
1332 }
1333
1334 $tools = [];
1335 foreach ( $items as $tool ) {
1336 $tools[] = Html::rawElement( 'span', [], $tool );
1337 }
1338 return ' <span class="mw-usertoollinks mw-changeslist-links">' .
1339 implode( ' ', $tools ) . '</span>';
1340 }
1341
1356 public static function userToolLinks(
1357 $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits = null,
1358 $useParentheses = true
1359 ) {
1360 if ( $userText === '' ) {
1361 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1362 'that need to be fixed?' );
1363 return ' ' . wfMessage( 'empty-username' )->parse();
1364 }
1365
1366 $items = self::userToolLinkArray( $userId, $userText, $redContribsWhenNoEdits, $flags, $edits );
1367 return self::renderUserToolLinksArray( $items, $useParentheses );
1368 }
1369
1379 public static function userToolLinksRedContribs(
1380 $userId, $userText, $edits = null, $useParentheses = true
1381 ) {
1382 return self::userToolLinks( $userId, $userText, true, 0, $edits, $useParentheses );
1383 }
1384
1391 public static function userTalkLink( $userId, $userText ) {
1392 if ( $userText === '' ) {
1393 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1394 'that need to be fixed?' );
1395 return wfMessage( 'empty-username' )->parse();
1396 }
1397
1398 $userTalkPage = TitleValue::tryNew( NS_USER_TALK, strtr( $userText, ' ', '_' ) );
1399 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-talk' ];
1400 $linkText = wfMessage( 'talkpagelinktext' )->escaped();
1401
1402 return $userTalkPage
1403 ? self::link( $userTalkPage, $linkText, $moreLinkAttribs )
1404 : Html::rawElement( 'span', $moreLinkAttribs, $linkText );
1405 }
1406
1413 public static function blockLink( $userId, $userText ) {
1414 if ( $userText === '' ) {
1415 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1416 'that need to be fixed?' );
1417 return wfMessage( 'empty-username' )->parse();
1418 }
1419
1420 $blockPage = SpecialPage::getTitleFor( 'Block', $userText );
1421 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-block' ];
1422
1423 return self::link( $blockPage,
1424 wfMessage( 'blocklink' )->escaped(),
1425 $moreLinkAttribs
1426 );
1427 }
1428
1434 public static function emailLink( $userId, $userText ) {
1435 if ( $userText === '' ) {
1436 wfLogWarning( __METHOD__ . ' received an empty username. Are there database errors ' .
1437 'that need to be fixed?' );
1438 return wfMessage( 'empty-username' )->parse();
1439 }
1440
1441 $emailPage = SpecialPage::getTitleFor( 'Emailuser', $userText );
1442 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-mail' ];
1443 return self::link( $emailPage,
1444 wfMessage( 'emaillink' )->escaped(),
1445 $moreLinkAttribs
1446 );
1447 }
1448
1460 public static function revUserLink( RevisionRecord $revRecord, $isPublic = false ) {
1461 // TODO inject authority
1462 $authority = RequestContext::getMain()->getAuthority();
1463
1464 $revUser = $revRecord->getUser(
1465 $isPublic ? RevisionRecord::FOR_PUBLIC : RevisionRecord::FOR_THIS_USER,
1466 $authority
1467 );
1468 if ( $revUser ) {
1469 $link = self::userLink( $revUser->getId(), $revUser->getName() );
1470 } else {
1471 // User is deleted and we can't (or don't want to) view it
1472 $link = wfMessage( 'rev-deleted-user' )->escaped();
1473 }
1474
1475 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
1476 $class = self::getRevisionDeletedClass( $revRecord );
1477 return '<span class="' . $class . '">' . $link . '</span>';
1478 }
1479 return $link;
1480 }
1481
1488 public static function getRevisionDeletedClass( RevisionRecord $revisionRecord ): string {
1489 $class = 'history-deleted';
1490 if ( $revisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
1491 $class .= ' mw-history-suppressed';
1492 }
1493 return $class;
1494 }
1495
1508 public static function revUserTools(
1509 RevisionRecord $revRecord,
1510 $isPublic = false,
1511 $useParentheses = true
1512 ) {
1513 // TODO inject authority
1514 $authority = RequestContext::getMain()->getAuthority();
1515
1516 $revUser = $revRecord->getUser(
1517 $isPublic ? RevisionRecord::FOR_PUBLIC : RevisionRecord::FOR_THIS_USER,
1518 $authority
1519 );
1520 if ( $revUser ) {
1521 $link = self::userLink(
1522 $revUser->getId(),
1523 $revUser->getName(),
1524 false,
1525 [ 'data-mw-revid' => $revRecord->getId() ]
1526 ) . self::userToolLinks(
1527 $revUser->getId(),
1528 $revUser->getName(),
1529 false,
1530 0,
1531 null,
1532 $useParentheses
1533 );
1534 } else {
1535 // User is deleted and we can't (or don't want to) view it
1536 $link = wfMessage( 'rev-deleted-user' )->escaped();
1537 }
1538
1539 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
1540 $class = self::getRevisionDeletedClass( $revRecord );
1541 return ' <span class="' . $class . ' mw-userlink">' . $link . '</span>';
1542 }
1543 return $link;
1544 }
1545
1556 public static function expandLocalLinks( string $html ) {
1557 return HtmlHelper::modifyElements(
1558 $html,
1559 static function ( SerializerNode $node ): bool {
1560 return $node->name === 'a' && isset( $node->attrs['href'] );
1561 },
1562 static function ( SerializerNode $node ): SerializerNode {
1563 $node->attrs['href'] =
1564 wfExpandUrl( $node->attrs['href'], PROTO_RELATIVE );
1565 return $node;
1566 }
1567 );
1568 }
1569
1590 public static function formatComment(
1591 $comment, $title = null, $local = false, $wikiId = null
1592 ) {
1593 wfDeprecated( __METHOD__, '1.41' );
1594 $formatter = MediaWikiServices::getInstance()->getCommentFormatter();
1595 return $formatter->format( $comment, $title, $local, $wikiId );
1596 }
1597
1617 public static function formatLinksInComment(
1618 $comment, $title = null, $local = false, $wikiId = null
1619 ) {
1620 wfDeprecated( __METHOD__, '1.41' );
1621 $formatter = MediaWikiServices::getInstance()->getCommentFormatter();
1622 return $formatter->formatLinksUnsafe( $comment, $title, $local, $wikiId );
1623 }
1624
1631 public static function normalizeSubpageLink( $contextTitle, $target, &$text ) {
1632 # Valid link forms:
1633 # Foobar -- normal
1634 # :Foobar -- override special treatment of prefix (images, language links)
1635 # /Foobar -- convert to CurrentPage/Foobar
1636 # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial and final / from text
1637 # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage
1638 # ../Foobar -- convert to CurrentPage/Foobar,
1639 # (from CurrentPage/CurrentSubPage)
1640 # ../Foobar/ -- convert to CurrentPage/Foobar, use 'Foobar' as text
1641 # (from CurrentPage/CurrentSubPage)
1642
1643 $ret = $target; # default return value is no change
1644
1645 # Some namespaces don't allow subpages,
1646 # so only perform processing if subpages are allowed
1647 if (
1648 $contextTitle && MediaWikiServices::getInstance()->getNamespaceInfo()->
1649 hasSubpages( $contextTitle->getNamespace() )
1650 ) {
1651 $hash = strpos( $target, '#' );
1652 if ( $hash !== false ) {
1653 $suffix = substr( $target, $hash );
1654 $target = substr( $target, 0, $hash );
1655 } else {
1656 $suffix = '';
1657 }
1658 # T9425
1659 $target = trim( $target );
1660 $contextPrefixedText = MediaWikiServices::getInstance()->getTitleFormatter()->
1661 getPrefixedText( $contextTitle );
1662 # Look at the first character
1663 if ( $target != '' && $target[0] === '/' ) {
1664 # / at end means we don't want the slash to be shown
1665 $m = [];
1666 $trailingSlashes = preg_match_all( '%(/+)$%', $target, $m );
1667 if ( $trailingSlashes ) {
1668 $noslash = $target = substr( $target, 1, -strlen( $m[0][0] ) );
1669 } else {
1670 $noslash = substr( $target, 1 );
1671 }
1672
1673 $ret = $contextPrefixedText . '/' . trim( $noslash ) . $suffix;
1674 if ( $text === '' ) {
1675 $text = $target . $suffix;
1676 } # this might be changed for ugliness reasons
1677 } else {
1678 # check for .. subpage backlinks
1679 $dotdotcount = 0;
1680 $nodotdot = $target;
1681 while ( str_starts_with( $nodotdot, '../' ) ) {
1682 ++$dotdotcount;
1683 $nodotdot = substr( $nodotdot, 3 );
1684 }
1685 if ( $dotdotcount > 0 ) {
1686 $exploded = explode( '/', $contextPrefixedText );
1687 if ( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page
1688 $ret = implode( '/', array_slice( $exploded, 0, -$dotdotcount ) );
1689 # / at the end means don't show full path
1690 if ( substr( $nodotdot, -1, 1 ) === '/' ) {
1691 $nodotdot = rtrim( $nodotdot, '/' );
1692 if ( $text === '' ) {
1693 $text = $nodotdot . $suffix;
1694 }
1695 }
1696 $nodotdot = trim( $nodotdot );
1697 if ( $nodotdot != '' ) {
1698 $ret .= '/' . $nodotdot;
1699 }
1700 $ret .= $suffix;
1701 }
1702 }
1703 }
1704 }
1705
1706 return $ret;
1707 }
1708
1728 public static function commentBlock(
1729 $comment, $title = null, $local = false, $wikiId = null, $useParentheses = true
1730 ) {
1731 wfDeprecated( __METHOD__, '1.41' );
1732 return MediaWikiServices::getInstance()->getCommentFormatter()
1733 ->formatBlock( $comment, $title, $local, $wikiId, $useParentheses );
1734 }
1735
1751 public static function revComment(
1752 RevisionRecord $revRecord,
1753 $local = false,
1754 $isPublic = false,
1755 $useParentheses = true
1756 ) {
1757 wfDeprecated( __METHOD__, '1.41' );
1758 $authority = RequestContext::getMain()->getAuthority();
1759 $formatter = MediaWikiServices::getInstance()->getCommentFormatter();
1760 return $formatter->formatRevision( $revRecord, $authority, $local, $isPublic, $useParentheses );
1761 }
1762
1768 public static function formatRevisionSize( $size ) {
1769 if ( $size == 0 ) {
1770 $stxt = wfMessage( 'historyempty' )->escaped();
1771 } else {
1772 $stxt = wfMessage( 'nbytes' )->numParams( $size )->escaped();
1773 }
1774 return "<span class=\"history-size mw-diff-bytes\" data-mw-bytes=\"$size\">$stxt</span>";
1775 }
1776
1784 public static function tocIndent() {
1785 wfDeprecated( __METHOD__, '1.42' );
1786 return "\n<ul>\n";
1787 }
1788
1797 public static function tocUnindent( $level ) {
1798 wfDeprecated( __METHOD__, '1.42' );
1799 return "</li>\n" . str_repeat( "</ul>\n</li>\n", $level > 0 ? $level : 0 );
1800 }
1801
1814 public static function tocLine( $linkAnchor, $tocline, $tocnumber, $level, $sectionIndex = false ) {
1815 wfDeprecated( __METHOD__, '1.42' );
1816 $classes = "toclevel-$level";
1817
1818 // Parser.php used to suppress tocLine by setting $sectionindex to false.
1819 // In those circumstances, we can now encounter '' or a "T-" prefixed index
1820 // for when the section comes from templates.
1821 if ( $sectionIndex !== false && $sectionIndex !== '' && !str_starts_with( $sectionIndex, "T-" ) ) {
1822 $classes .= " tocsection-$sectionIndex";
1823 }
1824
1825 // <li class="$classes"><a href="#$linkAnchor"><span class="tocnumber">
1826 // $tocnumber</span> <span class="toctext">$tocline</span></a>
1827 return Html::openElement( 'li', [ 'class' => $classes ] )
1828 . Html::rawElement( 'a',
1829 [ 'href' => "#$linkAnchor" ],
1830 Html::element( 'span', [ 'class' => 'tocnumber' ], $tocnumber )
1831 . ' '
1832 . Html::rawElement( 'span', [ 'class' => 'toctext' ], $tocline )
1833 );
1834 }
1835
1844 public static function tocLineEnd() {
1845 wfDeprecated( __METHOD__, '1.42' );
1846 return "</li>\n";
1847 }
1848
1858 public static function tocList( $toc, Language $lang = null ) {
1859 wfDeprecated( __METHOD__, '1.42' );
1860 $lang ??= RequestContext::getMain()->getLanguage();
1861
1862 $title = wfMessage( 'toc' )->inLanguage( $lang )->escaped();
1863
1864 return '<div id="toc" class="toc" role="navigation" aria-labelledby="mw-toc-heading">'
1865 . Html::element( 'input', [
1866 'type' => 'checkbox',
1867 'role' => 'button',
1868 'id' => 'toctogglecheckbox',
1869 'class' => 'toctogglecheckbox',
1870 'style' => 'display:none',
1871 ] )
1872 . Html::openElement( 'div', [
1873 'class' => 'toctitle',
1874 'lang' => $lang->getHtmlCode(),
1875 'dir' => $lang->getDir(),
1876 ] )
1877 . '<h2 id="mw-toc-heading">' . $title . '</h2>'
1878 . '<span class="toctogglespan">'
1879 . Html::label( '', 'toctogglecheckbox', [
1880 'class' => 'toctogglelabel',
1881 ] )
1882 . '</span>'
1883 . '</div>'
1884 . $toc
1885 . "</ul>\n</div>\n";
1886 }
1887
1900 public static function generateTOC( ?TOCData $tocData, Language $lang = null, array $options = [] ): string {
1901 wfDeprecated( __METHOD__, '1.42' );
1902 $toc = '';
1903 $lastLevel = 0;
1904 $maxTocLevel = $options['maxtoclevel'] ?? null;
1905 if ( $maxTocLevel === null ) {
1906 // Use wiki-configured default
1907 $services = MediaWikiServices::getInstance();
1908 $config = $services->getMainConfig();
1909 $maxTocLevel = $config->get( MainConfigNames::MaxTocLevel );
1910 }
1911 foreach ( ( $tocData ? $tocData->getSections() : [] ) as $section ) {
1912 $tocLevel = $section->tocLevel;
1913 if ( $tocLevel < $maxTocLevel ) {
1914 if ( $tocLevel > $lastLevel ) {
1915 $toc .= self::tocIndent();
1916 } elseif ( $tocLevel < $lastLevel ) {
1917 if ( $lastLevel < $maxTocLevel ) {
1918 $toc .= self::tocUnindent(
1919 $lastLevel - $tocLevel );
1920 } else {
1921 $toc .= self::tocLineEnd();
1922 }
1923 } else {
1924 $toc .= self::tocLineEnd();
1925 }
1926
1927 $toc .= self::tocLine( $section->linkAnchor,
1928 $section->line, $section->number,
1929 $tocLevel, $section->index );
1930 $lastLevel = $tocLevel;
1931 }
1932 }
1933 if ( $lastLevel < $maxTocLevel && $lastLevel > 0 ) {
1934 $toc .= self::tocUnindent( $lastLevel - 1 );
1935 }
1936 return self::tocList( $toc, $lang );
1937 }
1938
1956 public static function makeHeadline( $level, $attribs, $anchor, $html,
1957 $link, $fallbackAnchor = false
1958 ) {
1959 wfDeprecated( __METHOD__, '1.42' );
1960 $anchorEscaped = htmlspecialchars( $anchor, ENT_COMPAT );
1961 $fallback = '';
1962 if ( $fallbackAnchor !== false && $fallbackAnchor !== $anchor ) {
1963 $fallbackAnchor = htmlspecialchars( $fallbackAnchor, ENT_COMPAT );
1964 $fallback = "<span id=\"$fallbackAnchor\"></span>";
1965 }
1966 return "<h$level$attribs"
1967 . "$fallback<span class=\"mw-headline\" id=\"$anchorEscaped\">$html</span>"
1968 . $link
1969 . "</h$level>";
1970 }
1971
1978 public static function splitTrail( $trail ) {
1979 $regex = MediaWikiServices::getInstance()->getContentLanguage()->linkTrail();
1980 $inside = '';
1981 if ( $trail !== '' && preg_match( $regex, $trail, $m ) ) {
1982 [ , $inside, $trail ] = $m;
1983 }
1984 return [ $inside, $trail ];
1985 }
1986
2017 public static function generateRollback(
2018 RevisionRecord $revRecord,
2019 IContextSource $context = null,
2020 $options = []
2021 ) {
2022 $context ??= RequestContext::getMain();
2023
2024 $editCount = self::getRollbackEditCount( $revRecord );
2025 if ( $editCount === false ) {
2026 return '';
2027 }
2028
2029 $inner = self::buildRollbackLink( $revRecord, $context, $editCount );
2030
2031 $services = MediaWikiServices::getInstance();
2032 // Allow extensions to modify the rollback link.
2033 // Abort further execution if the extension wants full control over the link.
2034 if ( !( new HookRunner( $services->getHookContainer() ) )->onLinkerGenerateRollbackLink(
2035 $revRecord, $context, $options, $inner ) ) {
2036 return $inner;
2037 }
2038
2039 if ( !in_array( 'noBrackets', $options, true ) ) {
2040 $inner = $context->msg( 'brackets' )->rawParams( $inner )->escaped();
2041 }
2042
2043 if ( $services->getUserOptionsLookup()
2044 ->getBoolOption( $context->getUser(), 'showrollbackconfirmation' )
2045 ) {
2046 $stats = $services->getStatsdDataFactory();
2047 $stats->increment( 'rollbackconfirmation.event.load' );
2048 $context->getOutput()->addModules( 'mediawiki.misc-authed-curate' );
2049 }
2050
2051 return '<span class="mw-rollback-link">' . $inner . '</span>';
2052 }
2053
2072 public static function getRollbackEditCount( RevisionRecord $revRecord, $verify = true ) {
2073 if ( func_num_args() > 1 ) {
2074 wfDeprecated( __METHOD__ . ' with $verify parameter', '1.40' );
2075 }
2076 $showRollbackEditCount = MediaWikiServices::getInstance()->getMainConfig()
2077 ->get( MainConfigNames::ShowRollbackEditCount );
2078
2079 if ( !is_int( $showRollbackEditCount ) || !$showRollbackEditCount > 0 ) {
2080 // Nothing has happened, indicate this by returning 'null'
2081 return null;
2082 }
2083
2084 $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
2085
2086 // Up to the value of $wgShowRollbackEditCount revisions are counted
2087 $queryBuilder = MediaWikiServices::getInstance()->getRevisionStore()->newSelectQueryBuilder( $dbr );
2088 $res = $queryBuilder->where( [ 'rev_page' => $revRecord->getPageId() ] )
2089 ->useIndex( [ 'revision' => 'rev_page_timestamp' ] )
2090 ->orderBy( [ 'rev_timestamp', 'rev_id' ], SelectQueryBuilder::SORT_DESC )
2091 ->limit( $showRollbackEditCount + 1 )
2092 ->caller( __METHOD__ )->fetchResultSet();
2093
2094 $revUser = $revRecord->getUser( RevisionRecord::RAW );
2095 $revUserText = $revUser ? $revUser->getName() : '';
2096
2097 $editCount = 0;
2098 $moreRevs = false;
2099 foreach ( $res as $row ) {
2100 if ( $row->rev_user_text != $revUserText ) {
2101 if ( $row->rev_deleted & RevisionRecord::DELETED_TEXT
2102 || $row->rev_deleted & RevisionRecord::DELETED_USER
2103 ) {
2104 // If the user or the text of the revision we might rollback
2105 // to is deleted in some way we can't rollback. Similar to
2106 // the checks in WikiPage::commitRollback.
2107 return false;
2108 }
2109 $moreRevs = true;
2110 break;
2111 }
2112 $editCount++;
2113 }
2114
2115 if ( $editCount <= $showRollbackEditCount && !$moreRevs ) {
2116 // We didn't find at least $wgShowRollbackEditCount revisions made by the current user
2117 // and there weren't any other revisions. That means that the current user is the only
2118 // editor, so we can't rollback
2119 return false;
2120 }
2121 return $editCount;
2122 }
2123
2138 public static function buildRollbackLink(
2139 RevisionRecord $revRecord,
2140 IContextSource $context = null,
2141 $editCount = false
2142 ) {
2143 $config = MediaWikiServices::getInstance()->getMainConfig();
2144 $showRollbackEditCount = $config->get( MainConfigNames::ShowRollbackEditCount );
2145 $miserMode = $config->get( MainConfigNames::MiserMode );
2146 // To config which pages are affected by miser mode
2147 $disableRollbackEditCountSpecialPage = [ 'Recentchanges', 'Watchlist' ];
2148
2149 $context ??= RequestContext::getMain();
2150
2151 $title = $revRecord->getPageAsLinkTarget();
2152 $revUser = $revRecord->getUser();
2153 $revUserText = $revUser ? $revUser->getName() : '';
2154
2155 $query = [
2156 'action' => 'rollback',
2157 'from' => $revUserText,
2158 'token' => $context->getUser()->getEditToken( 'rollback' ),
2159 ];
2160
2161 $attrs = [
2162 'data-mw' => 'interface',
2163 'title' => $context->msg( 'tooltip-rollback' )->text()
2164 ];
2165
2166 $options = [ 'known', 'noclasses' ];
2167
2168 if ( $context->getRequest()->getBool( 'bot' ) ) {
2169 // T17999
2170 $query['hidediff'] = '1';
2171 $query['bot'] = '1';
2172 }
2173
2174 if ( $miserMode ) {
2175 foreach ( $disableRollbackEditCountSpecialPage as $specialPage ) {
2176 if ( $context->getTitle()->isSpecial( $specialPage ) ) {
2177 $showRollbackEditCount = false;
2178 break;
2179 }
2180 }
2181 }
2182
2183 // The edit count can be 0 on replica lag, fall back to the generic rollbacklink message
2184 $msg = [ 'rollbacklink' ];
2185 if ( is_int( $showRollbackEditCount ) && $showRollbackEditCount > 0 ) {
2186 if ( !is_numeric( $editCount ) ) {
2187 $editCount = self::getRollbackEditCount( $revRecord );
2188 }
2189
2190 if ( $editCount > $showRollbackEditCount ) {
2191 $msg = [ 'rollbacklinkcount-morethan', Message::numParam( $showRollbackEditCount ) ];
2192 } elseif ( $editCount ) {
2193 $msg = [ 'rollbacklinkcount', Message::numParam( $editCount ) ];
2194 }
2195 }
2196
2197 $html = $context->msg( ...$msg )->parse();
2198 return self::link( $title, $html, $attrs, $query, $options );
2199 }
2200
2209 public static function formatHiddenCategories( $hiddencats ) {
2210 $outText = '';
2211 if ( count( $hiddencats ) > 0 ) {
2212 # Construct the HTML
2213 $outText = '<div class="mw-hiddenCategoriesExplanation">';
2214 $outText .= wfMessage( 'hiddencategories' )->numParams( count( $hiddencats ) )->parseAsBlock();
2215 $outText .= "</div><ul>\n";
2216
2217 foreach ( $hiddencats as $titleObj ) {
2218 # If it's hidden, it must exist - no need to check with a LinkBatch
2219 $outText .= '<li>'
2220 . self::link( $titleObj, null, [], [], 'known' )
2221 . "</li>\n";
2222 }
2223 $outText .= '</ul>';
2224 }
2225 return $outText;
2226 }
2227
2231 private static function getContextFromMain() {
2232 $context = RequestContext::getMain();
2233 $context = new DerivativeContext( $context );
2234 return $context;
2235 }
2236
2254 public static function titleAttrib( $name, $options = null, array $msgParams = [], $localizer = null ) {
2255 if ( !$localizer ) {
2256 $localizer = self::getContextFromMain();
2257 }
2258 $message = $localizer->msg( "tooltip-$name", $msgParams );
2259 // Set a default tooltip for subject namespace tabs if that hasn't
2260 // been defined. See T22126
2261 if ( !$message->exists() && str_starts_with( $name, 'ca-nstab-' ) ) {
2262 $message = $localizer->msg( 'tooltip-ca-nstab' );
2263 }
2264
2265 if ( $message->isDisabled() ) {
2266 $tooltip = false;
2267 } else {
2268 $tooltip = $message->text();
2269 # Compatibility: formerly some tooltips had [alt-.] hardcoded
2270 $tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip );
2271 }
2272
2273 $options = (array)$options;
2274
2275 if ( in_array( 'nonexisting', $options ) ) {
2276 $tooltip = $localizer->msg( 'red-link-title', $tooltip ?: '' )->text();
2277 }
2278 if ( in_array( 'withaccess', $options ) ) {
2279 $accesskey = self::accesskey( $name, $localizer );
2280 if ( $accesskey !== false ) {
2281 // Should be build the same as in jquery.accessKeyLabel.js
2282 if ( $tooltip === false || $tooltip === '' ) {
2283 $tooltip = $localizer->msg( 'brackets', $accesskey )->text();
2284 } else {
2285 $tooltip .= $localizer->msg( 'word-separator' )->text();
2286 $tooltip .= $localizer->msg( 'brackets', $accesskey )->text();
2287 }
2288 }
2289 }
2290
2291 return $tooltip;
2292 }
2293
2294 public static $accesskeycache;
2295
2308 public static function accesskey( $name, $localizer = null ) {
2309 if ( !isset( self::$accesskeycache[$name] ) ) {
2310 if ( !$localizer ) {
2311 $localizer = self::getContextFromMain();
2312 }
2313 $msg = $localizer->msg( "accesskey-$name" );
2314 // Set a default accesskey for subject namespace tabs if an
2315 // accesskey has not been defined. See T22126
2316 if ( !$msg->exists() && str_starts_with( $name, 'ca-nstab-' ) ) {
2317 $msg = $localizer->msg( 'accesskey-ca-nstab' );
2318 }
2319 self::$accesskeycache[$name] = $msg->isDisabled() ? false : $msg->plain();
2320 }
2321 return self::$accesskeycache[$name];
2322 }
2323
2338 public static function getRevDeleteLink(
2339 Authority $performer,
2340 RevisionRecord $revRecord,
2341 LinkTarget $title
2342 ) {
2343 $canHide = $performer->isAllowed( 'deleterevision' );
2344 $canHideHistory = $performer->isAllowed( 'deletedhistory' );
2345 if ( !$canHide && !( $revRecord->getVisibility() && $canHideHistory ) ) {
2346 return '';
2347 }
2348
2349 if ( !$revRecord->userCan( RevisionRecord::DELETED_RESTRICTED, $performer ) ) {
2350 return self::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
2351 }
2352 $prefixedDbKey = MediaWikiServices::getInstance()->getTitleFormatter()->
2353 getPrefixedDBkey( $title );
2354 if ( $revRecord->getId() ) {
2355 // RevDelete links using revision ID are stable across
2356 // page deletion and undeletion; use when possible.
2357 $query = [
2358 'type' => 'revision',
2359 'target' => $prefixedDbKey,
2360 'ids' => $revRecord->getId()
2361 ];
2362 } else {
2363 // Older deleted entries didn't save a revision ID.
2364 // We have to refer to these by timestamp, ick!
2365 $query = [
2366 'type' => 'archive',
2367 'target' => $prefixedDbKey,
2368 'ids' => $revRecord->getTimestamp()
2369 ];
2370 }
2371 return self::revDeleteLink(
2372 $query,
2373 $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ),
2374 $canHide
2375 );
2376 }
2377
2390 public static function revDeleteLink( $query = [], $restricted = false, $delete = true ) {
2391 $sp = SpecialPage::getTitleFor( 'Revisiondelete' );
2392 $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
2393 $html = wfMessage( $msgKey )->escaped();
2394 $tag = $restricted ? 'strong' : 'span';
2395 $link = self::link( $sp, $html, [], $query, [ 'known', 'noclasses' ] );
2396 return Xml::tags(
2397 $tag,
2398 [ 'class' => 'mw-revdelundel-link' ],
2399 wfMessage( 'parentheses' )->rawParams( $link )->escaped()
2400 );
2401 }
2402
2414 public static function revDeleteLinkDisabled( $delete = true ) {
2415 $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
2416 $html = wfMessage( $msgKey )->escaped();
2417 $htmlParentheses = wfMessage( 'parentheses' )->rawParams( $html )->escaped();
2418 return Xml::tags( 'span', [ 'class' => 'mw-revdelundel-link' ], $htmlParentheses );
2419 }
2420
2434 public static function tooltipAndAccesskeyAttribs(
2435 $name,
2436 array $msgParams = [],
2437 $options = null,
2438 $localizer = null
2439 ) {
2440 $options = (array)$options;
2441 $options[] = 'withaccess';
2442
2443 // Get optional parameters from global context if any missing.
2444 if ( !$localizer ) {
2445 $localizer = self::getContextFromMain();
2446 }
2447
2448 $attribs = [
2449 'title' => self::titleAttrib( $name, $options, $msgParams, $localizer ),
2450 'accesskey' => self::accesskey( $name, $localizer )
2451 ];
2452 if ( $attribs['title'] === false ) {
2453 unset( $attribs['title'] );
2454 }
2455 if ( $attribs['accesskey'] === false ) {
2456 unset( $attribs['accesskey'] );
2457 }
2458 return $attribs;
2459 }
2460
2468 public static function tooltip( $name, $options = null ) {
2469 $tooltip = self::titleAttrib( $name, $options );
2470 if ( $tooltip === false ) {
2471 return '';
2472 }
2473 return Xml::expandAttributes( [
2474 'title' => $tooltip
2475 ] );
2476 }
2477
2478}
2479
2481class_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:204
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
array $params
The job parameters.
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:1556
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:1751
static revDeleteLink( $query=[], $restricted=false, $delete=true)
Creates a (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2390
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:1844
static blockLink( $userId, $userText)
Definition Linker.php:1413
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:2434
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:1391
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:1728
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:1590
static normalizeSubpageLink( $contextTitle, $target, &$text)
Definition Linker.php:1631
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:1356
static emailLink( $userId, $userText)
Definition Linker.php:1434
static formatHiddenCategories( $hiddencats)
Returns HTML for the "hidden categories on this page" list.
Definition Linker.php:2209
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:2072
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition Linker.php:1145
static userToolLinkArray( $userId, $userText, $redContribsWhenNoEdits=false, $flags=0, $edits=null)
Generate standard user tool links (talk, contributions, block link, etc.)
Definition Linker.php:1261
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:1900
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:2338
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:1858
static makeHeadline( $level, $attribs, $anchor, $html, $link, $fallbackAnchor=false)
Create a headline for content.
Definition Linker.php:1956
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:2468
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:2308
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:2254
static renderUserToolLinksArray(array $items, bool $useParentheses)
Generate standard tool links HTML from a link array returned by userToolLinkArray().
Definition Linker.php:1320
static userToolLinksRedContribs( $userId, $userText, $edits=null, $useParentheses=true)
Alias for userToolLinks( $userId, $userText, true );.
Definition Linker.php:1379
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:1978
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:2414
static formatRevisionSize( $size)
Definition Linker.php:1768
static tocUnindent( $level)
Finish one or more sublevels on the Table of Contents.
Definition Linker.php:1797
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:1617
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:1199
static revUserLink(RevisionRecord $revRecord, $isPublic=false)
Generate a user link if the current user is allowed to view it.
Definition Linker.php:1460
static getRevisionDeletedClass(RevisionRecord $revisionRecord)
Returns css class of a deleted revision.
Definition Linker.php:1488
static generateRollback(RevisionRecord $revRecord, IContextSource $context=null, $options=[])
Generate a rollback link for a given revision.
Definition Linker.php:2017
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:1508
static tocLine( $linkAnchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition Linker.php:1814
static buildRollbackLink(RevisionRecord $revRecord, IContextSource $context=null, $editCount=false)
Build a raw rollback link, useful for collections of "tool" links.
Definition Linker.php:2138
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:156
getBadFileLookup()
Get the BadFileLookup instance that this Parser is using.
Definition Parser.php:1263
getTargetLanguage()
Get the target language for the content being parsed.
Definition Parser.php:1189
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)