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
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 $toc = '';
1902 $lastLevel = 0;
1903 $maxTocLevel = $options['maxtoclevel'] ?? null;
1904 if ( $maxTocLevel === null ) {
1905 // Use wiki-configured default
1906 $services = MediaWikiServices::getInstance();
1907 $config = $services->getMainConfig();
1908 $maxTocLevel = $config->get( MainConfigNames::MaxTocLevel );
1909 }
1910 foreach ( ( $tocData ? $tocData->getSections() : [] ) as $section ) {
1911 $tocLevel = $section->tocLevel;
1912 if ( $tocLevel < $maxTocLevel ) {
1913 if ( $tocLevel > $lastLevel ) {
1914 $toc .= self::tocIndent();
1915 } elseif ( $tocLevel < $lastLevel ) {
1916 if ( $lastLevel < $maxTocLevel ) {
1917 $toc .= self::tocUnindent(
1918 $lastLevel - $tocLevel );
1919 } else {
1920 $toc .= self::tocLineEnd();
1921 }
1922 } else {
1923 $toc .= self::tocLineEnd();
1924 }
1925
1926 $toc .= self::tocLine( $section->linkAnchor,
1927 $section->line, $section->number,
1928 $tocLevel, $section->index );
1929 $lastLevel = $tocLevel;
1930 }
1931 }
1932 if ( $lastLevel < $maxTocLevel && $lastLevel > 0 ) {
1933 $toc .= self::tocUnindent( $lastLevel - 1 );
1934 }
1935 return self::tocList( $toc, $lang );
1936 }
1937
1955 public static function makeHeadline( $level, $attribs, $anchor, $html,
1956 $link, $fallbackAnchor = false
1957 ) {
1958 wfDeprecated( __METHOD__, '1.42' );
1959 $anchorEscaped = htmlspecialchars( $anchor, ENT_COMPAT );
1960 $fallback = '';
1961 if ( $fallbackAnchor !== false && $fallbackAnchor !== $anchor ) {
1962 $fallbackAnchor = htmlspecialchars( $fallbackAnchor, ENT_COMPAT );
1963 $fallback = "<span id=\"$fallbackAnchor\"></span>";
1964 }
1965 return "<h$level$attribs"
1966 . "$fallback<span class=\"mw-headline\" id=\"$anchorEscaped\">$html</span>"
1967 . $link
1968 . "</h$level>";
1969 }
1970
1977 public static function splitTrail( $trail ) {
1978 $regex = MediaWikiServices::getInstance()->getContentLanguage()->linkTrail();
1979 $inside = '';
1980 if ( $trail !== '' && preg_match( $regex, $trail, $m ) ) {
1981 [ , $inside, $trail ] = $m;
1982 }
1983 return [ $inside, $trail ];
1984 }
1985
2016 public static function generateRollback(
2017 RevisionRecord $revRecord,
2018 IContextSource $context = null,
2019 $options = []
2020 ) {
2021 $context ??= RequestContext::getMain();
2022
2023 $editCount = self::getRollbackEditCount( $revRecord );
2024 if ( $editCount === false ) {
2025 return '';
2026 }
2027
2028 $inner = self::buildRollbackLink( $revRecord, $context, $editCount );
2029
2030 $services = MediaWikiServices::getInstance();
2031 // Allow extensions to modify the rollback link.
2032 // Abort further execution if the extension wants full control over the link.
2033 if ( !( new HookRunner( $services->getHookContainer() ) )->onLinkerGenerateRollbackLink(
2034 $revRecord, $context, $options, $inner ) ) {
2035 return $inner;
2036 }
2037
2038 if ( !in_array( 'noBrackets', $options, true ) ) {
2039 $inner = $context->msg( 'brackets' )->rawParams( $inner )->escaped();
2040 }
2041
2042 if ( $services->getUserOptionsLookup()
2043 ->getBoolOption( $context->getUser(), 'showrollbackconfirmation' )
2044 ) {
2045 $stats = $services->getStatsdDataFactory();
2046 $stats->increment( 'rollbackconfirmation.event.load' );
2047 $context->getOutput()->addModules( 'mediawiki.misc-authed-curate' );
2048 }
2049
2050 return '<span class="mw-rollback-link">' . $inner . '</span>';
2051 }
2052
2071 public static function getRollbackEditCount( RevisionRecord $revRecord, $verify = true ) {
2072 if ( func_num_args() > 1 ) {
2073 wfDeprecated( __METHOD__ . ' with $verify parameter', '1.40' );
2074 }
2075 $showRollbackEditCount = MediaWikiServices::getInstance()->getMainConfig()
2076 ->get( MainConfigNames::ShowRollbackEditCount );
2077
2078 if ( !is_int( $showRollbackEditCount ) || !$showRollbackEditCount > 0 ) {
2079 // Nothing has happened, indicate this by returning 'null'
2080 return null;
2081 }
2082
2083 $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
2084
2085 // Up to the value of $wgShowRollbackEditCount revisions are counted
2086 $queryBuilder = MediaWikiServices::getInstance()->getRevisionStore()->newSelectQueryBuilder( $dbr );
2087 $res = $queryBuilder->where( [ 'rev_page' => $revRecord->getPageId() ] )
2088 ->useIndex( [ 'revision' => 'rev_page_timestamp' ] )
2089 ->orderBy( [ 'rev_timestamp', 'rev_id' ], SelectQueryBuilder::SORT_DESC )
2090 ->limit( $showRollbackEditCount + 1 )
2091 ->caller( __METHOD__ )->fetchResultSet();
2092
2093 $revUser = $revRecord->getUser( RevisionRecord::RAW );
2094 $revUserText = $revUser ? $revUser->getName() : '';
2095
2096 $editCount = 0;
2097 $moreRevs = false;
2098 foreach ( $res as $row ) {
2099 if ( $row->rev_user_text != $revUserText ) {
2100 if ( $row->rev_deleted & RevisionRecord::DELETED_TEXT
2101 || $row->rev_deleted & RevisionRecord::DELETED_USER
2102 ) {
2103 // If the user or the text of the revision we might rollback
2104 // to is deleted in some way we can't rollback. Similar to
2105 // the checks in WikiPage::commitRollback.
2106 return false;
2107 }
2108 $moreRevs = true;
2109 break;
2110 }
2111 $editCount++;
2112 }
2113
2114 if ( $editCount <= $showRollbackEditCount && !$moreRevs ) {
2115 // We didn't find at least $wgShowRollbackEditCount revisions made by the current user
2116 // and there weren't any other revisions. That means that the current user is the only
2117 // editor, so we can't rollback
2118 return false;
2119 }
2120 return $editCount;
2121 }
2122
2137 public static function buildRollbackLink(
2138 RevisionRecord $revRecord,
2139 IContextSource $context = null,
2140 $editCount = false
2141 ) {
2142 $config = MediaWikiServices::getInstance()->getMainConfig();
2143 $showRollbackEditCount = $config->get( MainConfigNames::ShowRollbackEditCount );
2144 $miserMode = $config->get( MainConfigNames::MiserMode );
2145 // To config which pages are affected by miser mode
2146 $disableRollbackEditCountSpecialPage = [ 'Recentchanges', 'Watchlist' ];
2147
2148 $context ??= RequestContext::getMain();
2149
2150 $title = $revRecord->getPageAsLinkTarget();
2151 $revUser = $revRecord->getUser();
2152 $revUserText = $revUser ? $revUser->getName() : '';
2153
2154 $query = [
2155 'action' => 'rollback',
2156 'from' => $revUserText,
2157 'token' => $context->getUser()->getEditToken( 'rollback' ),
2158 ];
2159
2160 $attrs = [
2161 'data-mw' => 'interface',
2162 'title' => $context->msg( 'tooltip-rollback' )->text()
2163 ];
2164
2165 $options = [ 'known', 'noclasses' ];
2166
2167 if ( $context->getRequest()->getBool( 'bot' ) ) {
2168 // T17999
2169 $query['hidediff'] = '1';
2170 $query['bot'] = '1';
2171 }
2172
2173 if ( $miserMode ) {
2174 foreach ( $disableRollbackEditCountSpecialPage as $specialPage ) {
2175 if ( $context->getTitle()->isSpecial( $specialPage ) ) {
2176 $showRollbackEditCount = false;
2177 break;
2178 }
2179 }
2180 }
2181
2182 // The edit count can be 0 on replica lag, fall back to the generic rollbacklink message
2183 $msg = [ 'rollbacklink' ];
2184 if ( is_int( $showRollbackEditCount ) && $showRollbackEditCount > 0 ) {
2185 if ( !is_numeric( $editCount ) ) {
2186 $editCount = self::getRollbackEditCount( $revRecord );
2187 }
2188
2189 if ( $editCount > $showRollbackEditCount ) {
2190 $msg = [ 'rollbacklinkcount-morethan', Message::numParam( $showRollbackEditCount ) ];
2191 } elseif ( $editCount ) {
2192 $msg = [ 'rollbacklinkcount', Message::numParam( $editCount ) ];
2193 }
2194 }
2195
2196 $html = $context->msg( ...$msg )->parse();
2197 return self::link( $title, $html, $attrs, $query, $options );
2198 }
2199
2208 public static function formatHiddenCategories( $hiddencats ) {
2209 $outText = '';
2210 if ( count( $hiddencats ) > 0 ) {
2211 # Construct the HTML
2212 $outText = '<div class="mw-hiddenCategoriesExplanation">';
2213 $outText .= wfMessage( 'hiddencategories' )->numParams( count( $hiddencats ) )->parseAsBlock();
2214 $outText .= "</div><ul>\n";
2215
2216 foreach ( $hiddencats as $titleObj ) {
2217 # If it's hidden, it must exist - no need to check with a LinkBatch
2218 $outText .= '<li>'
2219 . self::link( $titleObj, null, [], [], 'known' )
2220 . "</li>\n";
2221 }
2222 $outText .= '</ul>';
2223 }
2224 return $outText;
2225 }
2226
2230 private static function getContextFromMain() {
2231 $context = RequestContext::getMain();
2232 $context = new DerivativeContext( $context );
2233 return $context;
2234 }
2235
2253 public static function titleAttrib( $name, $options = null, array $msgParams = [], $localizer = null ) {
2254 if ( !$localizer ) {
2255 $localizer = self::getContextFromMain();
2256 }
2257 $message = $localizer->msg( "tooltip-$name", $msgParams );
2258 // Set a default tooltip for subject namespace tabs if that hasn't
2259 // been defined. See T22126
2260 if ( !$message->exists() && str_starts_with( $name, 'ca-nstab-' ) ) {
2261 $message = $localizer->msg( 'tooltip-ca-nstab' );
2262 }
2263
2264 if ( $message->isDisabled() ) {
2265 $tooltip = false;
2266 } else {
2267 $tooltip = $message->text();
2268 # Compatibility: formerly some tooltips had [alt-.] hardcoded
2269 $tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip );
2270 }
2271
2272 $options = (array)$options;
2273
2274 if ( in_array( 'nonexisting', $options ) ) {
2275 $tooltip = $localizer->msg( 'red-link-title', $tooltip ?: '' )->text();
2276 }
2277 if ( in_array( 'withaccess', $options ) ) {
2278 $accesskey = self::accesskey( $name, $localizer );
2279 if ( $accesskey !== false ) {
2280 // Should be build the same as in jquery.accessKeyLabel.js
2281 if ( $tooltip === false || $tooltip === '' ) {
2282 $tooltip = $localizer->msg( 'brackets', $accesskey )->text();
2283 } else {
2284 $tooltip .= $localizer->msg( 'word-separator' )->text();
2285 $tooltip .= $localizer->msg( 'brackets', $accesskey )->text();
2286 }
2287 }
2288 }
2289
2290 return $tooltip;
2291 }
2292
2293 public static $accesskeycache;
2294
2307 public static function accesskey( $name, $localizer = null ) {
2308 if ( !isset( self::$accesskeycache[$name] ) ) {
2309 if ( !$localizer ) {
2310 $localizer = self::getContextFromMain();
2311 }
2312 $msg = $localizer->msg( "accesskey-$name" );
2313 // Set a default accesskey for subject namespace tabs if an
2314 // accesskey has not been defined. See T22126
2315 if ( !$msg->exists() && str_starts_with( $name, 'ca-nstab-' ) ) {
2316 $msg = $localizer->msg( 'accesskey-ca-nstab' );
2317 }
2318 self::$accesskeycache[$name] = $msg->isDisabled() ? false : $msg->plain();
2319 }
2320 return self::$accesskeycache[$name];
2321 }
2322
2337 public static function getRevDeleteLink(
2338 Authority $performer,
2339 RevisionRecord $revRecord,
2340 LinkTarget $title
2341 ) {
2342 $canHide = $performer->isAllowed( 'deleterevision' );
2343 $canHideHistory = $performer->isAllowed( 'deletedhistory' );
2344 if ( !$canHide && !( $revRecord->getVisibility() && $canHideHistory ) ) {
2345 return '';
2346 }
2347
2348 if ( !$revRecord->userCan( RevisionRecord::DELETED_RESTRICTED, $performer ) ) {
2349 return self::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
2350 }
2351 $prefixedDbKey = MediaWikiServices::getInstance()->getTitleFormatter()->
2352 getPrefixedDBkey( $title );
2353 if ( $revRecord->getId() ) {
2354 // RevDelete links using revision ID are stable across
2355 // page deletion and undeletion; use when possible.
2356 $query = [
2357 'type' => 'revision',
2358 'target' => $prefixedDbKey,
2359 'ids' => $revRecord->getId()
2360 ];
2361 } else {
2362 // Older deleted entries didn't save a revision ID.
2363 // We have to refer to these by timestamp, ick!
2364 $query = [
2365 'type' => 'archive',
2366 'target' => $prefixedDbKey,
2367 'ids' => $revRecord->getTimestamp()
2368 ];
2369 }
2370 return self::revDeleteLink(
2371 $query,
2372 $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ),
2373 $canHide
2374 );
2375 }
2376
2389 public static function revDeleteLink( $query = [], $restricted = false, $delete = true ) {
2390 $sp = SpecialPage::getTitleFor( 'Revisiondelete' );
2391 $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
2392 $html = wfMessage( $msgKey )->escaped();
2393 $tag = $restricted ? 'strong' : 'span';
2394 $link = self::link( $sp, $html, [], $query, [ 'known', 'noclasses' ] );
2395 return Xml::tags(
2396 $tag,
2397 [ 'class' => 'mw-revdelundel-link' ],
2398 wfMessage( 'parentheses' )->rawParams( $link )->escaped()
2399 );
2400 }
2401
2413 public static function revDeleteLinkDisabled( $delete = true ) {
2414 $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
2415 $html = wfMessage( $msgKey )->escaped();
2416 $htmlParentheses = wfMessage( 'parentheses' )->rawParams( $html )->escaped();
2417 return Xml::tags( 'span', [ 'class' => 'mw-revdelundel-link' ], $htmlParentheses );
2418 }
2419
2433 public static function tooltipAndAccesskeyAttribs(
2434 $name,
2435 array $msgParams = [],
2436 $options = null,
2437 $localizer = null
2438 ) {
2439 $options = (array)$options;
2440 $options[] = 'withaccess';
2441
2442 // Get optional parameters from global context if any missing.
2443 if ( !$localizer ) {
2444 $localizer = self::getContextFromMain();
2445 }
2446
2447 $attribs = [
2448 'title' => self::titleAttrib( $name, $options, $msgParams, $localizer ),
2449 'accesskey' => self::accesskey( $name, $localizer )
2450 ];
2451 if ( $attribs['title'] === false ) {
2452 unset( $attribs['title'] );
2453 }
2454 if ( $attribs['accesskey'] === false ) {
2455 unset( $attribs['accesskey'] );
2456 }
2457 return $attribs;
2458 }
2459
2467 public static function tooltip( $name, $options = null ) {
2468 $tooltip = self::titleAttrib( $name, $options );
2469 if ( $tooltip === false ) {
2470 return '';
2471 }
2472 return Xml::expandAttributes( [
2473 'title' => $tooltip
2474 ] );
2475 }
2476
2477}
2478
2482class_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
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:2389
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:2433
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:2208
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:2071
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:2337
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:1955
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:2467
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:2307
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:2253
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:1977
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:2413
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:2016
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:2137
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)