MediaWiki REL1_41
Linker.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\Linker;
24
27use File;
28use HtmlArmor;
30use Language;
45use Message;
47use Parser;
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
114 public static function link(
115 $target, $html = null, $customAttribs = [], $query = [], $options = []
116 ) {
117 if ( !$target instanceof LinkTarget ) {
118 wfWarn( __METHOD__ . ': Requires $target to be a LinkTarget object.', 2 );
119 return "<!-- ERROR -->$html";
120 }
121
122 $services = MediaWikiServices::getInstance();
123 $options = (array)$options;
124 if ( $options ) {
125 // Custom options, create new LinkRenderer
126 $linkRenderer = $services->getLinkRendererFactory()
127 ->createFromLegacyOptions( $options );
128 } else {
129 $linkRenderer = $services->getLinkRenderer();
130 }
131
132 if ( $html !== null ) {
133 $text = new HtmlArmor( $html );
134 } else {
135 $text = null;
136 }
137
138 if ( in_array( 'known', $options, true ) ) {
139 return $linkRenderer->makeKnownLink( $target, $text, $customAttribs, $query );
140 }
141
142 if ( in_array( 'broken', $options, true ) ) {
143 return $linkRenderer->makeBrokenLink( $target, $text, $customAttribs, $query );
144 }
145
146 if ( in_array( 'noclasses', $options, true ) ) {
147 return $linkRenderer->makePreloadedLink( $target, $text, '', $customAttribs, $query );
148 }
149
150 return $linkRenderer->makeLink( $target, $text, $customAttribs, $query );
151 }
152
172 public static function linkKnown(
173 $target, $html = null, $customAttribs = [],
174 $query = [], $options = [ 'known' ]
175 ) {
176 return self::link( $target, $html, $customAttribs, $query, $options );
177 }
178
196 public static function makeSelfLinkObj( $nt, $html = '', $query = '', $trail = '', $prefix = '', $hash = '' ) {
197 $nt = Title::newFromLinkTarget( $nt );
198 $attrs = [];
199 if ( $hash ) {
200 $attrs['class'] = 'mw-selflink-fragment';
201 $attrs['href'] = '#' . $hash;
202 } else {
203 // For backwards compatibility with gadgets we add selflink as well.
204 $attrs['class'] = 'mw-selflink selflink';
205 }
206 $ret = Html::rawElement( 'a', $attrs, $prefix . $html ) . $trail;
207 $hookRunner = new HookRunner( MediaWikiServices::getInstance()->getHookContainer() );
208 if ( !$hookRunner->onSelfLinkBegin( $nt, $html, $trail, $prefix, $ret ) ) {
209 return $ret;
210 }
211
212 if ( $html == '' ) {
213 $html = htmlspecialchars( $nt->getPrefixedText() );
214 }
215 [ $inside, $trail ] = self::splitTrail( $trail );
216 return Html::rawElement( 'a', $attrs, $prefix . $html . $inside ) . $trail;
217 }
218
229 public static function getInvalidTitleDescription( IContextSource $context, $namespace, $title ) {
230 // First we check whether the namespace exists or not.
231 if ( MediaWikiServices::getInstance()->getNamespaceInfo()->exists( $namespace ) ) {
232 if ( $namespace == NS_MAIN ) {
233 $name = $context->msg( 'blanknamespace' )->text();
234 } else {
235 $name = MediaWikiServices::getInstance()->getContentLanguage()->
236 getFormattedNsText( $namespace );
237 }
238 return $context->msg( 'invalidtitle-knownnamespace', $namespace, $name, $title )->text();
239 }
240
241 return $context->msg( 'invalidtitle-unknownnamespace', $namespace, $title )->text();
242 }
243
252 private static function fnamePart( $url ) {
253 $basename = strrchr( $url, '/' );
254 if ( $basename === false ) {
255 $basename = $url;
256 } else {
257 $basename = substr( $basename, 1 );
258 }
259 return $basename;
260 }
261
272 public static function makeExternalImage( $url, $alt = '' ) {
273 if ( $alt == '' ) {
274 $alt = self::fnamePart( $url );
275 }
276 $img = '';
277 $success = ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )
278 ->onLinkerMakeExternalImage( $url, $alt, $img );
279 if ( !$success ) {
280 wfDebug( "Hook LinkerMakeExternalImage changed the output of external image "
281 . "with url {$url} and alt text {$alt} to {$img}" );
282 return $img;
283 }
284 return Html::element( 'img',
285 [
286 'src' => $url,
287 'alt' => $alt
288 ]
289 );
290 }
291
330 public static function makeImageLink( Parser $parser, LinkTarget $title,
331 $file, $frameParams = [], $handlerParams = [], $time = false,
332 $query = '', $widthOption = null
333 ) {
334 $title = Title::newFromLinkTarget( $title );
335 $res = 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 } elseif ( isset( $frameParams['framed'] ) ) {
712 // Use image dimensions, don't scale
713 $thumb = $file->getUnscaledThumb( $handlerParams );
714 $noscale = true;
715 $rdfaType = 'mw:File/Frame';
716 } else {
717 # Do not present an image bigger than the source, for bitmap-style images
718 # This is a hack to maintain compatibility with arbitrary pre-1.10 behavior
719 $srcWidth = $file->getWidth( $page );
720 if ( $srcWidth && !$file->mustRender() && $handlerParams['width'] > $srcWidth ) {
721 $handlerParams['width'] = $srcWidth;
722 }
723 $thumb = $file->transform( $handlerParams );
724 }
725
726 if ( $thumb ) {
727 $outerWidth = $thumb->getWidth() + 2;
728 } else {
729 $outerWidth = $handlerParams['width'] + 2;
730 }
731 }
732
733 if ( !$enableLegacyMediaDOM && $parser && $rdfaType === 'mw:File/Thumb' ) {
734 $parser->getOutput()->addModules( [ 'mediawiki.page.media' ] );
735 }
736
737 $url = Title::newFromLinkTarget( $title )->getLocalURL( $query );
738 $linkTitleQuery = [];
739 if ( $page || $lang ) {
740 if ( $page ) {
741 $linkTitleQuery['page'] = $page;
742 }
743 if ( $lang ) {
744 $linkTitleQuery['lang'] = $lang;
745 }
746 # ThumbnailImage::toHtml() already adds page= onto the end of DjVu URLs
747 # So we don't need to pass it here in $query. However, the URL for the
748 # zoom icon still needs it, so we make a unique query for it. See T16771
749 $url = wfAppendQuery( $url, $linkTitleQuery );
750 }
751
752 if ( $manualthumb
753 && !isset( $frameParams['link-title'] )
754 && !isset( $frameParams['link-url'] )
755 && !isset( $frameParams['no-link'] ) ) {
756 $frameParams['link-title'] = $title;
757 $frameParams['link-title-query'] = $linkTitleQuery;
758 }
759
760 if ( $frameParams['align'] != '' ) {
761 // Possible values: mw-halign-left mw-halign-center mw-halign-right mw-halign-none
762 $classes[] = "mw-halign-{$frameParams['align']}";
763 }
764
765 if ( isset( $frameParams['class'] ) ) {
766 $classes[] = $frameParams['class'];
767 }
768
769 $s = '';
770
771 if ( $enableLegacyMediaDOM ) {
772 $s .= "<div class=\"thumb t{$frameParams['align']}\">"
773 . "<div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
774 }
775
776 $isBadFile = $exists && $thumb && $parser &&
777 $parser->getBadFileLookup()->isBadFile(
778 $manualthumb ? $manual_title : $title->getDBkey(),
779 $parser->getTitle()
780 );
781
782 if ( !$exists ) {
783 $rdfaType = 'mw:Error ' . $rdfaType;
784 $label = '';
785 if ( !$enableLegacyMediaDOM ) {
786 $label = $frameParams['alt'] ?? '';
787 }
789 $title, $label, '', '', '', (bool)$time, $handlerParams, false
790 );
791 $zoomIcon = '';
792 } elseif ( !$thumb || ( !$enableLegacyMediaDOM && $thumb->isError() ) || $isBadFile ) {
793 $rdfaType = 'mw:Error ' . $rdfaType;
794 if ( $enableLegacyMediaDOM ) {
795 if ( !$thumb ) {
796 $s .= wfMessage( 'thumbnail_error', '' )->escaped();
797 } else {
799 $title, '', '', '', '', (bool)$time, $handlerParams, true
800 );
801 }
802 } else {
803 if ( $thumb && $thumb->isError() ) {
804 Assert::invariant(
805 $thumb instanceof MediaTransformError,
806 'Unknown MediaTransformOutput: ' . get_class( $thumb )
807 );
808 $label = $thumb->toText();
809 } elseif ( !$thumb ) {
810 $label = wfMessage( 'thumbnail_error', '' )->text();
811 } else {
812 $label = '';
813 }
815 $title, $label, '', '', '', (bool)$time, $handlerParams, true
816 );
817 }
818 $zoomIcon = '';
819 } else {
820 if ( !$noscale && !$manualthumb ) {
821 self::processResponsiveImages( $file, $thumb, $handlerParams );
822 }
823 $params = [];
824 // An empty alt indicates an image is not a key part of the content
825 // and that non-visual browsers may omit it from rendering. Only
826 // set the parameter if it's explicitly requested.
827 if ( isset( $frameParams['alt'] ) ) {
828 $params['alt'] = $frameParams['alt'];
829 }
830 if ( $enableLegacyMediaDOM ) {
831 $params += [
832 'img-class' => ( isset( $frameParams['class'] ) && $frameParams['class'] !== ''
833 ? $frameParams['class'] . ' '
834 : '' ) . 'thumbimage'
835 ];
836 } else {
837 $params += [
838 'img-class' => 'mw-file-element',
839 ];
840 // Only thumbs gets the magnify link
841 if ( $rdfaType === 'mw:File/Thumb' ) {
842 $params['magnify-resource'] = $url;
843 }
844 }
845 $params = self::getImageLinkMTOParams( $frameParams, $query, $parser ) + $params;
846 $s .= $thumb->toHtml( $params );
847 if ( isset( $frameParams['framed'] ) ) {
848 $zoomIcon = '';
849 } else {
850 $zoomIcon = Html::rawElement( 'div', [ 'class' => 'magnify' ],
851 Html::rawElement( 'a', [
852 'href' => $url,
853 'class' => 'internal',
854 'title' => wfMessage( 'thumbnail-more' )->text(),
855 ] )
856 );
857 }
858 }
859
860 if ( $enableLegacyMediaDOM ) {
861 $s .= ' <div class="thumbcaption">' . $zoomIcon . $frameParams['caption'] . '</div></div></div>';
862 return str_replace( "\n", ' ', $s );
863 }
864
865 $s .= Html::rawElement(
866 'figcaption', [], $frameParams['caption'] ?? ''
867 );
868
869 $attribs = [
870 'class' => $classes,
871 'typeof' => $rdfaType,
872 ];
873
874 $s = Html::rawElement( 'figure', $attribs, $s );
875
876 return str_replace( "\n", ' ', $s );
877 }
878
887 public static function processResponsiveImages( $file, $thumb, $hp ) {
888 $responsiveImages = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::ResponsiveImages );
889 if ( $responsiveImages && $thumb && !$thumb->isError() ) {
890 $hp15 = $hp;
891 $hp15['width'] = round( $hp['width'] * 1.5 );
892 $hp20 = $hp;
893 $hp20['width'] = $hp['width'] * 2;
894 if ( isset( $hp['height'] ) ) {
895 $hp15['height'] = round( $hp['height'] * 1.5 );
896 $hp20['height'] = $hp['height'] * 2;
897 }
898
899 $thumb15 = $file->transform( $hp15 );
900 $thumb20 = $file->transform( $hp20 );
901 if ( $thumb15 && !$thumb15->isError() && $thumb15->getUrl() !== $thumb->getUrl() ) {
902 $thumb->responsiveUrls['1.5'] = $thumb15->getUrl();
903 }
904 if ( $thumb20 && !$thumb20->isError() && $thumb20->getUrl() !== $thumb->getUrl() ) {
905 $thumb->responsiveUrls['2'] = $thumb20->getUrl();
906 }
907 }
908 }
909
924 public static function makeBrokenImageLinkObj(
925 $title, $label = '', $query = '', $unused1 = '', $unused2 = '',
926 $time = false, array $handlerParams = [], bool $currentExists = false
927 ) {
928 if ( !$title instanceof LinkTarget ) {
929 wfWarn( __METHOD__ . ': Requires $title to be a LinkTarget object.' );
930 return "<!-- ERROR -->" . htmlspecialchars( $label );
931 }
932
933 $title = Title::newFromLinkTarget( $title );
934 $services = MediaWikiServices::getInstance();
935 $mainConfig = $services->getMainConfig();
936 $enableUploads = $mainConfig->get( MainConfigNames::EnableUploads );
937 $uploadMissingFileUrl = $mainConfig->get( MainConfigNames::UploadMissingFileUrl );
938 $uploadNavigationUrl = $mainConfig->get( MainConfigNames::UploadNavigationUrl );
939 if ( $label == '' ) {
940 $label = $title->getPrefixedText();
941 }
942
943 $html = Html::element( 'span', [
944 'class' => 'mw-file-element mw-broken-media',
945 // These data attributes are used to dynamically size the span, see T273013
946 'data-width' => $handlerParams['width'] ?? null,
947 'data-height' => $handlerParams['height'] ?? null,
948 ], $label );
949
950 if ( $mainConfig->get( MainConfigNames::ParserEnableLegacyMediaDOM ) ) {
951 $html = htmlspecialchars( $label, ENT_COMPAT );
952 }
953
954 $repoGroup = $services->getRepoGroup();
955 $currentExists = $currentExists ||
956 ( $time && $repoGroup->findFile( $title ) !== false );
957
958 if ( ( $uploadMissingFileUrl || $uploadNavigationUrl || $enableUploads )
959 && !$currentExists
960 ) {
961 if (
962 $title->inNamespace( NS_FILE ) &&
963 $repoGroup->getLocalRepo()->checkRedirect( $title )
964 ) {
965 // We already know it's a redirect, so mark it accordingly
966 return self::link(
967 $title,
968 $html,
969 [ 'class' => 'mw-redirect' ],
970 wfCgiToArray( $query ),
971 [ 'known', 'noclasses' ]
972 );
973 }
974 return Html::rawElement( 'a', [
975 'href' => self::getUploadUrl( $title, $query ),
976 'class' => 'new',
977 'title' => $title->getPrefixedText()
978 ], $html );
979 }
980 return self::link(
981 $title,
982 $html,
983 [],
984 wfCgiToArray( $query ),
985 [ 'known', 'noclasses' ]
986 );
987 }
988
997 protected static function getUploadUrl( $destFile, $query = '' ) {
998 $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
999 $uploadMissingFileUrl = $mainConfig->get( MainConfigNames::UploadMissingFileUrl );
1000 $uploadNavigationUrl = $mainConfig->get( MainConfigNames::UploadNavigationUrl );
1001 $q = 'wpDestFile=' . Title::newFromLinkTarget( $destFile )->getPartialURL();
1002 if ( $query != '' ) {
1003 $q .= '&' . $query;
1004 }
1005
1006 if ( $uploadMissingFileUrl ) {
1007 return wfAppendQuery( $uploadMissingFileUrl, $q );
1008 }
1009
1010 if ( $uploadNavigationUrl ) {
1011 return wfAppendQuery( $uploadNavigationUrl, $q );
1012 }
1013
1014 $upload = SpecialPage::getTitleFor( 'Upload' );
1015
1016 return $upload->getLocalURL( $q );
1017 }
1018
1028 public static function makeMediaLinkObj( $title, $html = '', $time = false ) {
1029 $img = MediaWikiServices::getInstance()->getRepoGroup()->findFile(
1030 $title, [ 'time' => $time ]
1031 );
1032 return self::makeMediaLinkFile( $title, $img, $html );
1033 }
1034
1047 public static function makeMediaLinkFile( LinkTarget $title, $file, $html = '' ) {
1048 if ( $file && $file->exists() ) {
1049 $url = $file->getUrl();
1050 $class = 'internal';
1051 } else {
1052 $url = self::getUploadUrl( $title );
1053 $class = 'new';
1054 }
1055
1056 $alt = $title->getText();
1057 if ( $html == '' ) {
1058 $html = $alt;
1059 }
1060
1061 $ret = '';
1062 $attribs = [
1063 'href' => $url,
1064 'class' => $class,
1065 'title' => $alt
1066 ];
1067
1068 if ( !( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )->onLinkerMakeMediaLinkFile(
1069 Title::newFromLinkTarget( $title ), $file, $html, $attribs, $ret )
1070 ) {
1071 wfDebug( "Hook LinkerMakeMediaLinkFile changed the output of link "
1072 . "with url {$url} and text {$html} to {$ret}" );
1073 return $ret;
1074 }
1075
1076 return Html::rawElement( 'a', $attribs, $html );
1077 }
1078
1089 public static function specialLink( $name, $key = '' ) {
1090 $queryPos = strpos( $name, '?' );
1091 if ( $queryPos !== false ) {
1092 $getParams = wfCgiToArray( substr( $name, $queryPos + 1 ) );
1093 $name = substr( $name, 0, $queryPos );
1094 } else {
1095 $getParams = [];
1096 }
1097
1098 $slashPos = strpos( $name, '/' );
1099 if ( $slashPos !== false ) {
1100 $subpage = substr( $name, $slashPos + 1 );
1101 $name = substr( $name, 0, $slashPos );
1102 } else {
1103 $subpage = false;
1104 }
1105
1106 if ( $key == '' ) {
1107 $key = strtolower( $name );
1108 }
1109
1110 return self::linkKnown(
1111 SpecialPage::getTitleFor( $name, $subpage ),
1112 wfMessage( $key )->escaped(),
1113 [],
1114 $getParams
1115 );
1116 }
1117
1136 public static function makeExternalLink( $url, $text, $escape = true,
1137 $linktype = '', $attribs = [], $title = null
1138 ) {
1139 global $wgTitle;
1140 $class = 'external';
1141 if ( $linktype ) {
1142 $class .= " $linktype";
1143 }
1144 if ( isset( $attribs['class'] ) && $attribs['class'] ) {
1145 $class .= " {$attribs['class']}";
1146 }
1147 $attribs['class'] = $class;
1148
1149 if ( $escape ) {
1150 $text = htmlspecialchars( $text, ENT_COMPAT );
1151 }
1152
1153 if ( !$title ) {
1154 $title = $wgTitle;
1155 }
1156 $newRel = Parser::getExternalLinkRel( $url, $title );
1157 if ( !isset( $attribs['rel'] ) || $attribs['rel'] === '' ) {
1158 $attribs['rel'] = $newRel;
1159 } elseif ( $newRel !== null ) {
1160 // Merge the rel attributes.
1161 $newRels = explode( ' ', $newRel );
1162 $oldRels = explode( ' ', $attribs['rel'] );
1163 $combined = array_unique( array_merge( $newRels, $oldRels ) );
1164 $attribs['rel'] = implode( ' ', $combined );
1165 }
1166 $link = '';
1167 $success = ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )->onLinkerMakeExternalLink(
1168 $url, $text, $link, $attribs, $linktype );
1169 if ( !$success ) {
1170 wfDebug( "Hook LinkerMakeExternalLink changed the output of link "
1171 . "with url {$url} and text {$text} to {$link}" );
1172 return $link;
1173 }
1174 $attribs['href'] = $url;
1175 return Html::rawElement( 'a', $attribs, $text );
1176 }
1177
1190 public static function userLink(
1191 $userId,
1192 $userName,
1193 $altUserName = false,
1194 $attributes = []
1195 ) {
1196 if ( $userName === '' || $userName === false || $userName === null ) {
1197 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1198 'that need to be fixed?' );
1199 return wfMessage( 'empty-username' )->parse();
1200 }
1201
1202 $classes = 'mw-userlink';
1203 if ( MediaWikiServices::getInstance()->getTempUserConfig()->isTempName( $userName ) ) {
1204 $classes .= ' mw-tempuserlink';
1205 $page = SpecialPage::getTitleValueFor( 'Contributions', $userName );
1206 } elseif ( $userId == 0 ) {
1207 $page = ExternalUserNames::getUserLinkTitle( $userName );
1208
1209 if ( ExternalUserNames::isExternal( $userName ) ) {
1210 $classes .= ' mw-extuserlink';
1211 } elseif ( $altUserName === false ) {
1212 $altUserName = IPUtils::prettifyIP( $userName );
1213 }
1214 $classes .= ' mw-anonuserlink'; // Separate link class for anons (T45179)
1215 } else {
1216 $page = TitleValue::tryNew( NS_USER, strtr( $userName, ' ', '_' ) );
1217 }
1218
1219 // Wrap the output with <bdi> tags for directionality isolation
1220 $linkText =
1221 '<bdi>' . htmlspecialchars( $altUserName !== false ? $altUserName : $userName ) . '</bdi>';
1222
1223 if ( isset( $attributes['class'] ) ) {
1224 $attributes['class'] .= ' ' . $classes;
1225 } else {
1226 $attributes['class'] = $classes;
1227 }
1228
1229 return $page
1230 ? self::link( $page, $linkText, $attributes )
1231 : Html::rawElement( 'span', $attributes, $linkText );
1232 }
1233
1248 public static function userToolLinks(
1249 $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits = null,
1250 $useParentheses = true
1251 ) {
1252 if ( $userText === '' ) {
1253 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1254 'that need to be fixed?' );
1255 return ' ' . wfMessage( 'empty-username' )->parse();
1256 }
1257 global $wgLang;
1258 $services = MediaWikiServices::getInstance();
1259 $disableAnonTalk = $services->getMainConfig()->get( MainConfigNames::DisableAnonTalk );
1260 $talkable = !( $disableAnonTalk && $userId == 0 );
1261 $blockable = !( $flags & self::TOOL_LINKS_NOBLOCK );
1262 $addEmailLink = $flags & self::TOOL_LINKS_EMAIL && $userId;
1263
1264 if ( $userId == 0 && ExternalUserNames::isExternal( $userText ) ) {
1265 // No tools for an external user
1266 return '';
1267 }
1268
1269 $items = [];
1270 if ( $talkable ) {
1271 $items[] = self::userTalkLink( $userId, $userText );
1272 }
1273 if ( $userId ) {
1274 // check if the user has an edit
1275 $attribs = [];
1276 $attribs['class'] = 'mw-usertoollinks-contribs';
1277 if ( $redContribsWhenNoEdits ) {
1278 if ( intval( $edits ) === 0 && $edits !== 0 ) {
1279 $user = User::newFromId( $userId );
1280 $edits = $user->getEditCount();
1281 }
1282 if ( $edits === 0 ) {
1283 // Note: "new" class is inappropriate here, as "new" class
1284 // should only be used for pages that do not exist.
1285 $attribs['class'] .= ' mw-usertoollinks-contribs-no-edits';
1286 }
1287 }
1288 $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText );
1289
1290 $items[] = self::link( $contribsPage, wfMessage( 'contribslink' )->escaped(), $attribs );
1291 }
1292 $userCanBlock = RequestContext::getMain()->getAuthority()->isAllowed( 'block' );
1293 if ( $blockable && $userCanBlock ) {
1294 $items[] = self::blockLink( $userId, $userText );
1295 }
1296
1297 $user = RequestContext::getMain()->getUser();
1298 if ( $addEmailLink && $user->canSendEmail() ) {
1299 $items[] = self::emailLink( $userId, $userText );
1300 }
1301
1302 ( new HookRunner( $services->getHookContainer() ) )->onUserToolLinksEdit( $userId, $userText, $items );
1303
1304 if ( !$items ) {
1305 return '';
1306 }
1307
1308 if ( $useParentheses ) {
1309 return wfMessage( 'word-separator' )->escaped()
1310 . '<span class="mw-usertoollinks">'
1311 . wfMessage( 'parentheses' )->rawParams( $wgLang->pipeList( $items ) )->escaped()
1312 . '</span>';
1313 }
1314
1315 $tools = [];
1316 foreach ( $items as $tool ) {
1317 $tools[] = Html::rawElement( 'span', [], $tool );
1318 }
1319 return ' <span class="mw-usertoollinks mw-changeslist-links">' .
1320 implode( ' ', $tools ) . '</span>';
1321 }
1322
1332 public static function userToolLinksRedContribs(
1333 $userId, $userText, $edits = null, $useParentheses = true
1334 ) {
1335 return self::userToolLinks( $userId, $userText, true, 0, $edits, $useParentheses );
1336 }
1337
1344 public static function userTalkLink( $userId, $userText ) {
1345 if ( $userText === '' ) {
1346 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1347 'that need to be fixed?' );
1348 return wfMessage( 'empty-username' )->parse();
1349 }
1350
1351 $userTalkPage = TitleValue::tryNew( NS_USER_TALK, strtr( $userText, ' ', '_' ) );
1352 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-talk' ];
1353 $linkText = wfMessage( 'talkpagelinktext' )->escaped();
1354
1355 return $userTalkPage
1356 ? self::link( $userTalkPage, $linkText, $moreLinkAttribs )
1357 : Html::rawElement( 'span', $moreLinkAttribs, $linkText );
1358 }
1359
1366 public static function blockLink( $userId, $userText ) {
1367 if ( $userText === '' ) {
1368 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1369 'that need to be fixed?' );
1370 return wfMessage( 'empty-username' )->parse();
1371 }
1372
1373 $blockPage = SpecialPage::getTitleFor( 'Block', $userText );
1374 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-block' ];
1375
1376 return self::link( $blockPage,
1377 wfMessage( 'blocklink' )->escaped(),
1378 $moreLinkAttribs
1379 );
1380 }
1381
1387 public static function emailLink( $userId, $userText ) {
1388 if ( $userText === '' ) {
1389 wfLogWarning( __METHOD__ . ' received an empty username. Are there database errors ' .
1390 'that need to be fixed?' );
1391 return wfMessage( 'empty-username' )->parse();
1392 }
1393
1394 $emailPage = SpecialPage::getTitleFor( 'Emailuser', $userText );
1395 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-mail' ];
1396 return self::link( $emailPage,
1397 wfMessage( 'emaillink' )->escaped(),
1398 $moreLinkAttribs
1399 );
1400 }
1401
1413 public static function revUserLink( RevisionRecord $revRecord, $isPublic = false ) {
1414 // TODO inject authority
1415 $authority = RequestContext::getMain()->getAuthority();
1416
1417 $revUser = $revRecord->getUser(
1418 $isPublic ? RevisionRecord::FOR_PUBLIC : RevisionRecord::FOR_THIS_USER,
1419 $authority
1420 );
1421 if ( $revUser ) {
1422 $link = self::userLink( $revUser->getId(), $revUser->getName() );
1423 } else {
1424 // User is deleted and we can't (or don't want to) view it
1425 $link = wfMessage( 'rev-deleted-user' )->escaped();
1426 }
1427
1428 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
1429 $class = self::getRevisionDeletedClass( $revRecord );
1430 return '<span class="' . $class . '">' . $link . '</span>';
1431 }
1432 return $link;
1433 }
1434
1441 public static function getRevisionDeletedClass( RevisionRecord $revisionRecord ): string {
1442 $class = 'history-deleted';
1443 if ( $revisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
1444 $class .= ' mw-history-suppressed';
1445 }
1446 return $class;
1447 }
1448
1461 public static function revUserTools(
1462 RevisionRecord $revRecord,
1463 $isPublic = false,
1464 $useParentheses = true
1465 ) {
1466 // TODO inject authority
1467 $authority = RequestContext::getMain()->getAuthority();
1468
1469 $revUser = $revRecord->getUser(
1470 $isPublic ? RevisionRecord::FOR_PUBLIC : RevisionRecord::FOR_THIS_USER,
1471 $authority
1472 );
1473 if ( $revUser ) {
1474 $link = self::userLink(
1475 $revUser->getId(),
1476 $revUser->getName(),
1477 false,
1478 [ 'data-mw-revid' => $revRecord->getId() ]
1479 ) . self::userToolLinks(
1480 $revUser->getId(),
1481 $revUser->getName(),
1482 false,
1483 0,
1484 null,
1485 $useParentheses
1486 );
1487 } else {
1488 // User is deleted and we can't (or don't want to) view it
1489 $link = wfMessage( 'rev-deleted-user' )->escaped();
1490 }
1491
1492 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
1493 $class = self::getRevisionDeletedClass( $revRecord );
1494 return ' <span class="' . $class . ' mw-userlink">' . $link . '</span>';
1495 }
1496 return $link;
1497 }
1498
1509 public static function expandLocalLinks( string $html ) {
1510 return HtmlHelper::modifyElements(
1511 $html,
1512 static function ( SerializerNode $node ): bool {
1513 return $node->name === 'a' && isset( $node->attrs['href'] );
1514 },
1515 static function ( SerializerNode $node ): SerializerNode {
1516 $node->attrs['href'] =
1517 wfExpandUrl( $node->attrs['href'], PROTO_RELATIVE );
1518 return $node;
1519 }
1520 );
1521 }
1522
1543 public static function formatComment(
1544 $comment, $title = null, $local = false, $wikiId = null
1545 ) {
1546 wfDeprecated( __METHOD__, '1.41' );
1547 $formatter = MediaWikiServices::getInstance()->getCommentFormatter();
1548 return $formatter->format( $comment, $title, $local, $wikiId );
1549 }
1550
1570 public static function formatLinksInComment(
1571 $comment, $title = null, $local = false, $wikiId = null
1572 ) {
1573 wfDeprecated( __METHOD__, '1.41' );
1574 $formatter = MediaWikiServices::getInstance()->getCommentFormatter();
1575 return $formatter->formatLinksUnsafe( $comment, $title, $local, $wikiId );
1576 }
1577
1584 public static function normalizeSubpageLink( $contextTitle, $target, &$text ) {
1585 # Valid link forms:
1586 # Foobar -- normal
1587 # :Foobar -- override special treatment of prefix (images, language links)
1588 # /Foobar -- convert to CurrentPage/Foobar
1589 # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial and final / from text
1590 # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage
1591 # ../Foobar -- convert to CurrentPage/Foobar,
1592 # (from CurrentPage/CurrentSubPage)
1593 # ../Foobar/ -- convert to CurrentPage/Foobar, use 'Foobar' as text
1594 # (from CurrentPage/CurrentSubPage)
1595
1596 $ret = $target; # default return value is no change
1597
1598 # Some namespaces don't allow subpages,
1599 # so only perform processing if subpages are allowed
1600 if (
1601 $contextTitle && MediaWikiServices::getInstance()->getNamespaceInfo()->
1602 hasSubpages( $contextTitle->getNamespace() )
1603 ) {
1604 $hash = strpos( $target, '#' );
1605 if ( $hash !== false ) {
1606 $suffix = substr( $target, $hash );
1607 $target = substr( $target, 0, $hash );
1608 } else {
1609 $suffix = '';
1610 }
1611 # T9425
1612 $target = trim( $target );
1613 $contextPrefixedText = MediaWikiServices::getInstance()->getTitleFormatter()->
1614 getPrefixedText( $contextTitle );
1615 # Look at the first character
1616 if ( $target != '' && $target[0] === '/' ) {
1617 # / at end means we don't want the slash to be shown
1618 $m = [];
1619 $trailingSlashes = preg_match_all( '%(/+)$%', $target, $m );
1620 if ( $trailingSlashes ) {
1621 $noslash = $target = substr( $target, 1, -strlen( $m[0][0] ) );
1622 } else {
1623 $noslash = substr( $target, 1 );
1624 }
1625
1626 $ret = $contextPrefixedText . '/' . trim( $noslash ) . $suffix;
1627 if ( $text === '' ) {
1628 $text = $target . $suffix;
1629 } # this might be changed for ugliness reasons
1630 } else {
1631 # check for .. subpage backlinks
1632 $dotdotcount = 0;
1633 $nodotdot = $target;
1634 while ( str_starts_with( $nodotdot, '../' ) ) {
1635 ++$dotdotcount;
1636 $nodotdot = substr( $nodotdot, 3 );
1637 }
1638 if ( $dotdotcount > 0 ) {
1639 $exploded = explode( '/', $contextPrefixedText );
1640 if ( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page
1641 $ret = implode( '/', array_slice( $exploded, 0, -$dotdotcount ) );
1642 # / at the end means don't show full path
1643 if ( substr( $nodotdot, -1, 1 ) === '/' ) {
1644 $nodotdot = rtrim( $nodotdot, '/' );
1645 if ( $text === '' ) {
1646 $text = $nodotdot . $suffix;
1647 }
1648 }
1649 $nodotdot = trim( $nodotdot );
1650 if ( $nodotdot != '' ) {
1651 $ret .= '/' . $nodotdot;
1652 }
1653 $ret .= $suffix;
1654 }
1655 }
1656 }
1657 }
1658
1659 return $ret;
1660 }
1661
1681 public static function commentBlock(
1682 $comment, $title = null, $local = false, $wikiId = null, $useParentheses = true
1683 ) {
1684 wfDeprecated( __METHOD__, '1.41' );
1685 return MediaWikiServices::getInstance()->getCommentFormatter()
1686 ->formatBlock( $comment, $title, $local, $wikiId, $useParentheses );
1687 }
1688
1704 public static function revComment(
1705 RevisionRecord $revRecord,
1706 $local = false,
1707 $isPublic = false,
1708 $useParentheses = true
1709 ) {
1710 wfDeprecated( __METHOD__, '1.41' );
1711 $authority = RequestContext::getMain()->getAuthority();
1712 $formatter = MediaWikiServices::getInstance()->getCommentFormatter();
1713 return $formatter->formatRevision( $revRecord, $authority, $local, $isPublic, $useParentheses );
1714 }
1715
1721 public static function formatRevisionSize( $size ) {
1722 if ( $size == 0 ) {
1723 $stxt = wfMessage( 'historyempty' )->escaped();
1724 } else {
1725 $stxt = wfMessage( 'nbytes' )->numParams( $size )->escaped();
1726 }
1727 return "<span class=\"history-size mw-diff-bytes\" data-mw-bytes=\"$size\">$stxt</span>";
1728 }
1729
1736 public static function tocIndent() {
1737 return "\n<ul>\n";
1738 }
1739
1747 public static function tocUnindent( $level ) {
1748 return "</li>\n" . str_repeat( "</ul>\n</li>\n", $level > 0 ? $level : 0 );
1749 }
1750
1762 public static function tocLine( $linkAnchor, $tocline, $tocnumber, $level, $sectionIndex = false ) {
1763 $classes = "toclevel-$level";
1764
1765 // Parser.php used to suppress tocLine by setting $sectionindex to false.
1766 // In those circumstances, we can now encounter '' or a "T-" prefixed index
1767 // for when the section comes from templates.
1768 if ( $sectionIndex !== false && $sectionIndex !== '' && !str_starts_with( $sectionIndex, "T-" ) ) {
1769 $classes .= " tocsection-$sectionIndex";
1770 }
1771
1772 // <li class="$classes"><a href="#$linkAnchor"><span class="tocnumber">
1773 // $tocnumber</span> <span class="toctext">$tocline</span></a>
1774 return Html::openElement( 'li', [ 'class' => $classes ] )
1775 . Html::rawElement( 'a',
1776 [ 'href' => "#$linkAnchor" ],
1777 Html::element( 'span', [ 'class' => 'tocnumber' ], $tocnumber )
1778 . ' '
1779 . Html::rawElement( 'span', [ 'class' => 'toctext' ], $tocline )
1780 );
1781 }
1782
1790 public static function tocLineEnd() {
1791 return "</li>\n";
1792 }
1793
1802 public static function tocList( $toc, Language $lang = null ) {
1803 $lang ??= RequestContext::getMain()->getLanguage();
1804
1805 $title = wfMessage( 'toc' )->inLanguage( $lang )->escaped();
1806
1807 return '<div id="toc" class="toc" role="navigation" aria-labelledby="mw-toc-heading">'
1808 . Html::element( 'input', [
1809 'type' => 'checkbox',
1810 'role' => 'button',
1811 'id' => 'toctogglecheckbox',
1812 'class' => 'toctogglecheckbox',
1813 'style' => 'display:none',
1814 ] )
1815 . Html::openElement( 'div', [
1816 'class' => 'toctitle',
1817 'lang' => $lang->getHtmlCode(),
1818 'dir' => $lang->getDir(),
1819 ] )
1820 . '<h2 id="mw-toc-heading">' . $title . '</h2>'
1821 . '<span class="toctogglespan">'
1822 . Html::label( '', 'toctogglecheckbox', [
1823 'class' => 'toctogglelabel',
1824 ] )
1825 . '</span>'
1826 . '</div>'
1827 . $toc
1828 . "</ul>\n</div>\n";
1829 }
1830
1842 public static function generateTOC( ?TOCData $tocData, Language $lang = null, array $options = [] ): string {
1843 $toc = '';
1844 $lastLevel = 0;
1845 $maxTocLevel = $options['maxtoclevel'] ?? null;
1846 if ( $maxTocLevel === null ) {
1847 // Use wiki-configured default
1848 $services = MediaWikiServices::getInstance();
1849 $config = $services->getMainConfig();
1850 $maxTocLevel = $config->get( MainConfigNames::MaxTocLevel );
1851 }
1852 foreach ( ( $tocData ? $tocData->getSections() : [] ) as $section ) {
1853 $tocLevel = $section->tocLevel;
1854 if ( $tocLevel < $maxTocLevel ) {
1855 if ( $tocLevel > $lastLevel ) {
1856 $toc .= self::tocIndent();
1857 } elseif ( $tocLevel < $lastLevel ) {
1858 if ( $lastLevel < $maxTocLevel ) {
1859 $toc .= self::tocUnindent(
1860 $lastLevel - $tocLevel );
1861 } else {
1862 $toc .= self::tocLineEnd();
1863 }
1864 } else {
1865 $toc .= self::tocLineEnd();
1866 }
1867
1868 $toc .= self::tocLine( $section->linkAnchor,
1869 $section->line, $section->number,
1870 $tocLevel, $section->index );
1871 $lastLevel = $tocLevel;
1872 }
1873 }
1874 if ( $lastLevel < $maxTocLevel && $lastLevel > 0 ) {
1875 $toc .= self::tocUnindent( $lastLevel - 1 );
1876 }
1877 return self::tocList( $toc, $lang );
1878 }
1879
1896 public static function makeHeadline( $level, $attribs, $anchor, $html,
1897 $link, $fallbackAnchor = false
1898 ) {
1899 $anchorEscaped = htmlspecialchars( $anchor, ENT_COMPAT );
1900 $fallback = '';
1901 if ( $fallbackAnchor !== false && $fallbackAnchor !== $anchor ) {
1902 $fallbackAnchor = htmlspecialchars( $fallbackAnchor, ENT_COMPAT );
1903 $fallback = "<span id=\"$fallbackAnchor\"></span>";
1904 }
1905 return "<h$level$attribs"
1906 . "$fallback<span class=\"mw-headline\" id=\"$anchorEscaped\">$html</span>"
1907 . $link
1908 . "</h$level>";
1909 }
1910
1917 public static function splitTrail( $trail ) {
1918 $regex = MediaWikiServices::getInstance()->getContentLanguage()->linkTrail();
1919 $inside = '';
1920 if ( $trail !== '' && preg_match( $regex, $trail, $m ) ) {
1921 [ , $inside, $trail ] = $m;
1922 }
1923 return [ $inside, $trail ];
1924 }
1925
1956 public static function generateRollback(
1957 RevisionRecord $revRecord,
1958 IContextSource $context = null,
1959 $options = []
1960 ) {
1961 $context ??= RequestContext::getMain();
1962
1963 $editCount = self::getRollbackEditCount( $revRecord );
1964 if ( $editCount === false ) {
1965 return '';
1966 }
1967
1968 $inner = self::buildRollbackLink( $revRecord, $context, $editCount );
1969
1970 $services = MediaWikiServices::getInstance();
1971 // Allow extensions to modify the rollback link.
1972 // Abort further execution if the extension wants full control over the link.
1973 if ( !( new HookRunner( $services->getHookContainer() ) )->onLinkerGenerateRollbackLink(
1974 $revRecord, $context, $options, $inner ) ) {
1975 return $inner;
1976 }
1977
1978 if ( !in_array( 'noBrackets', $options, true ) ) {
1979 $inner = $context->msg( 'brackets' )->rawParams( $inner )->escaped();
1980 }
1981
1982 if ( $services->getUserOptionsLookup()
1983 ->getBoolOption( $context->getUser(), 'showrollbackconfirmation' )
1984 ) {
1985 $stats = $services->getStatsdDataFactory();
1986 $stats->increment( 'rollbackconfirmation.event.load' );
1987 $context->getOutput()->addModules( 'mediawiki.misc-authed-curate' );
1988 }
1989
1990 return '<span class="mw-rollback-link">' . $inner . '</span>';
1991 }
1992
2011 public static function getRollbackEditCount( RevisionRecord $revRecord, $verify = true ) {
2012 if ( func_num_args() > 1 ) {
2013 wfDeprecated( __METHOD__ . ' with $verify parameter', '1.40' );
2014 }
2015 $showRollbackEditCount = MediaWikiServices::getInstance()->getMainConfig()
2016 ->get( MainConfigNames::ShowRollbackEditCount );
2017
2018 if ( !is_int( $showRollbackEditCount ) || !$showRollbackEditCount > 0 ) {
2019 // Nothing has happened, indicate this by returning 'null'
2020 return null;
2021 }
2022
2023 $dbr = MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->getReplicaDatabase();
2024
2025 // Up to the value of $wgShowRollbackEditCount revisions are counted
2026 $queryBuilder = MediaWikiServices::getInstance()->getRevisionStore()->newSelectQueryBuilder( $dbr );
2027 $res = $queryBuilder->where( [ 'rev_page' => $revRecord->getPageId() ] )
2028 ->useIndex( [ 'revision' => 'rev_page_timestamp' ] )
2029 ->orderBy( [ 'rev_timestamp', 'rev_id' ], SelectQueryBuilder::SORT_DESC )
2030 ->limit( $showRollbackEditCount + 1 )
2031 ->caller( __METHOD__ )->fetchResultSet();
2032
2033 $revUser = $revRecord->getUser( RevisionRecord::RAW );
2034 $revUserText = $revUser ? $revUser->getName() : '';
2035
2036 $editCount = 0;
2037 $moreRevs = false;
2038 foreach ( $res as $row ) {
2039 if ( $row->rev_user_text != $revUserText ) {
2040 if ( $row->rev_deleted & RevisionRecord::DELETED_TEXT
2041 || $row->rev_deleted & RevisionRecord::DELETED_USER
2042 ) {
2043 // If the user or the text of the revision we might rollback
2044 // to is deleted in some way we can't rollback. Similar to
2045 // the checks in WikiPage::commitRollback.
2046 return false;
2047 }
2048 $moreRevs = true;
2049 break;
2050 }
2051 $editCount++;
2052 }
2053
2054 if ( $editCount <= $showRollbackEditCount && !$moreRevs ) {
2055 // We didn't find at least $wgShowRollbackEditCount revisions made by the current user
2056 // and there weren't any other revisions. That means that the current user is the only
2057 // editor, so we can't rollback
2058 return false;
2059 }
2060 return $editCount;
2061 }
2062
2077 public static function buildRollbackLink(
2078 RevisionRecord $revRecord,
2079 IContextSource $context = null,
2080 $editCount = false
2081 ) {
2082 $config = MediaWikiServices::getInstance()->getMainConfig();
2083 $showRollbackEditCount = $config->get( MainConfigNames::ShowRollbackEditCount );
2084 $miserMode = $config->get( MainConfigNames::MiserMode );
2085 // To config which pages are affected by miser mode
2086 $disableRollbackEditCountSpecialPage = [ 'Recentchanges', 'Watchlist' ];
2087
2088 $context ??= RequestContext::getMain();
2089
2090 $title = $revRecord->getPageAsLinkTarget();
2091 $revUser = $revRecord->getUser();
2092 $revUserText = $revUser ? $revUser->getName() : '';
2093
2094 $query = [
2095 'action' => 'rollback',
2096 'from' => $revUserText,
2097 'token' => $context->getUser()->getEditToken( 'rollback' ),
2098 ];
2099
2100 $attrs = [
2101 'data-mw' => 'interface',
2102 'title' => $context->msg( 'tooltip-rollback' )->text()
2103 ];
2104
2105 $options = [ 'known', 'noclasses' ];
2106
2107 if ( $context->getRequest()->getBool( 'bot' ) ) {
2108 // T17999
2109 $query['hidediff'] = '1';
2110 $query['bot'] = '1';
2111 }
2112
2113 if ( $miserMode ) {
2114 foreach ( $disableRollbackEditCountSpecialPage as $specialPage ) {
2115 if ( $context->getTitle()->isSpecial( $specialPage ) ) {
2116 $showRollbackEditCount = false;
2117 break;
2118 }
2119 }
2120 }
2121
2122 // The edit count can be 0 on replica lag, fall back to the generic rollbacklink message
2123 $msg = [ 'rollbacklink' ];
2124 if ( is_int( $showRollbackEditCount ) && $showRollbackEditCount > 0 ) {
2125 if ( !is_numeric( $editCount ) ) {
2126 $editCount = self::getRollbackEditCount( $revRecord );
2127 }
2128
2129 if ( $editCount > $showRollbackEditCount ) {
2130 $msg = [ 'rollbacklinkcount-morethan', Message::numParam( $showRollbackEditCount ) ];
2131 } elseif ( $editCount ) {
2132 $msg = [ 'rollbacklinkcount', Message::numParam( $editCount ) ];
2133 }
2134 }
2135
2136 $html = $context->msg( ...$msg )->parse();
2137 return self::link( $title, $html, $attrs, $query, $options );
2138 }
2139
2148 public static function formatHiddenCategories( $hiddencats ) {
2149 $outText = '';
2150 if ( count( $hiddencats ) > 0 ) {
2151 # Construct the HTML
2152 $outText = '<div class="mw-hiddenCategoriesExplanation">';
2153 $outText .= wfMessage( 'hiddencategories' )->numParams( count( $hiddencats ) )->parseAsBlock();
2154 $outText .= "</div><ul>\n";
2155
2156 foreach ( $hiddencats as $titleObj ) {
2157 # If it's hidden, it must exist - no need to check with a LinkBatch
2158 $outText .= '<li>'
2159 . self::link( $titleObj, null, [], [], 'known' )
2160 . "</li>\n";
2161 }
2162 $outText .= '</ul>';
2163 }
2164 return $outText;
2165 }
2166
2170 private static function getContextFromMain() {
2171 $context = RequestContext::getMain();
2172 $context = new DerivativeContext( $context );
2173 return $context;
2174 }
2175
2193 public static function titleAttrib( $name, $options = null, array $msgParams = [], $localizer = null ) {
2194 if ( !$localizer ) {
2195 $localizer = self::getContextFromMain();
2196 }
2197 $message = $localizer->msg( "tooltip-$name", $msgParams );
2198 // Set a default tooltip for subject namespace tabs if that hasn't
2199 // been defined. See T22126
2200 if ( !$message->exists() && str_starts_with( $name, 'ca-nstab-' ) ) {
2201 $message = $localizer->msg( 'tooltip-ca-nstab' );
2202 }
2203
2204 if ( $message->isDisabled() ) {
2205 $tooltip = false;
2206 } else {
2207 $tooltip = $message->text();
2208 # Compatibility: formerly some tooltips had [alt-.] hardcoded
2209 $tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip );
2210 }
2211
2212 $options = (array)$options;
2213
2214 if ( in_array( 'nonexisting', $options ) ) {
2215 $tooltip = $localizer->msg( 'red-link-title', $tooltip ?: '' )->text();
2216 }
2217 if ( in_array( 'withaccess', $options ) ) {
2218 $accesskey = self::accesskey( $name, $localizer );
2219 if ( $accesskey !== false ) {
2220 // Should be build the same as in jquery.accessKeyLabel.js
2221 if ( $tooltip === false || $tooltip === '' ) {
2222 $tooltip = $localizer->msg( 'brackets', $accesskey )->text();
2223 } else {
2224 $tooltip .= $localizer->msg( 'word-separator' )->text();
2225 $tooltip .= $localizer->msg( 'brackets', $accesskey )->text();
2226 }
2227 }
2228 }
2229
2230 return $tooltip;
2231 }
2232
2233 public static $accesskeycache;
2234
2247 public static function accesskey( $name, $localizer = null ) {
2248 if ( !isset( self::$accesskeycache[$name] ) ) {
2249 if ( !$localizer ) {
2250 $localizer = self::getContextFromMain();
2251 }
2252 $msg = $localizer->msg( "accesskey-$name" );
2253 // Set a default accesskey for subject namespace tabs if an
2254 // accesskey has not been defined. See T22126
2255 if ( !$msg->exists() && str_starts_with( $name, 'ca-nstab-' ) ) {
2256 $msg = $localizer->msg( 'accesskey-ca-nstab' );
2257 }
2258 self::$accesskeycache[$name] = $msg->isDisabled() ? false : $msg->plain();
2259 }
2260 return self::$accesskeycache[$name];
2261 }
2262
2277 public static function getRevDeleteLink(
2278 Authority $performer,
2279 RevisionRecord $revRecord,
2280 LinkTarget $title
2281 ) {
2282 $canHide = $performer->isAllowed( 'deleterevision' );
2283 $canHideHistory = $performer->isAllowed( 'deletedhistory' );
2284 if ( !$canHide && !( $revRecord->getVisibility() && $canHideHistory ) ) {
2285 return '';
2286 }
2287
2288 if ( !$revRecord->userCan( RevisionRecord::DELETED_RESTRICTED, $performer ) ) {
2289 return self::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
2290 }
2291 $prefixedDbKey = MediaWikiServices::getInstance()->getTitleFormatter()->
2292 getPrefixedDBkey( $title );
2293 if ( $revRecord->getId() ) {
2294 // RevDelete links using revision ID are stable across
2295 // page deletion and undeletion; use when possible.
2296 $query = [
2297 'type' => 'revision',
2298 'target' => $prefixedDbKey,
2299 'ids' => $revRecord->getId()
2300 ];
2301 } else {
2302 // Older deleted entries didn't save a revision ID.
2303 // We have to refer to these by timestamp, ick!
2304 $query = [
2305 'type' => 'archive',
2306 'target' => $prefixedDbKey,
2307 'ids' => $revRecord->getTimestamp()
2308 ];
2309 }
2310 return self::revDeleteLink(
2311 $query,
2312 $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ),
2313 $canHide
2314 );
2315 }
2316
2329 public static function revDeleteLink( $query = [], $restricted = false, $delete = true ) {
2330 $sp = SpecialPage::getTitleFor( 'Revisiondelete' );
2331 $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
2332 $html = wfMessage( $msgKey )->escaped();
2333 $tag = $restricted ? 'strong' : 'span';
2334 $link = self::link( $sp, $html, [], $query, [ 'known', 'noclasses' ] );
2335 return Xml::tags(
2336 $tag,
2337 [ 'class' => 'mw-revdelundel-link' ],
2338 wfMessage( 'parentheses' )->rawParams( $link )->escaped()
2339 );
2340 }
2341
2353 public static function revDeleteLinkDisabled( $delete = true ) {
2354 $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
2355 $html = wfMessage( $msgKey )->escaped();
2356 $htmlParentheses = wfMessage( 'parentheses' )->rawParams( $html )->escaped();
2357 return Xml::tags( 'span', [ 'class' => 'mw-revdelundel-link' ], $htmlParentheses );
2358 }
2359
2373 public static function tooltipAndAccesskeyAttribs(
2374 $name,
2375 array $msgParams = [],
2376 $options = null,
2377 $localizer = null
2378 ) {
2379 $options = (array)$options;
2380 $options[] = 'withaccess';
2381
2382 // Get optional parameters from global context if any missing.
2383 if ( !$localizer ) {
2384 $localizer = self::getContextFromMain();
2385 }
2386
2387 $attribs = [
2388 'title' => self::titleAttrib( $name, $options, $msgParams, $localizer ),
2389 'accesskey' => self::accesskey( $name, $localizer )
2390 ];
2391 if ( $attribs['title'] === false ) {
2392 unset( $attribs['title'] );
2393 }
2394 if ( $attribs['accesskey'] === false ) {
2395 unset( $attribs['accesskey'] );
2396 }
2397 return $attribs;
2398 }
2399
2407 public static function tooltip( $name, $options = null ) {
2408 $tooltip = self::titleAttrib( $name, $options );
2409 if ( $tooltip === false ) {
2410 return '';
2411 }
2412 return Xml::expandAttributes( [
2413 'title' => $tooltip
2414 ] );
2415 }
2416
2417}
2418
2422class_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:193
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
Definition MessagesAb.php:8
if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode $wgLang
Definition Setup.php:535
if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode $wgTitle
Definition Setup.php:535
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 ...
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition File.php:70
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().
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:57
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:1509
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:1704
static revDeleteLink( $query=[], $restricted=false, $delete=true)
Creates a (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2329
static link( $target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition Linker.php:114
static tocLineEnd()
End a Table Of Contents line.
Definition Linker.php:1790
static blockLink( $userId, $userText)
Definition Linker.php:1366
static makeSelfLinkObj( $nt, $html='', $query='', $trail='', $prefix='', $hash='')
Make appropriate markup for a link to the current article.
Definition Linker.php:196
static tooltipAndAccesskeyAttribs( $name, array $msgParams=[], $options=null, $localizer=null)
Returns the attributes for the tooltip and access key.
Definition Linker.php:2373
static getUploadUrl( $destFile, $query='')
Get the URL to upload a certain file.
Definition Linker.php:997
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:330
static makeMediaLinkObj( $title, $html='', $time=false)
Create a direct link to a given uploaded file.
Definition Linker.php:1028
static processResponsiveImages( $file, $thumb, $hp)
Process responsive images: add 1.5x and 2x subimages to the thumbnail, where applicable.
Definition Linker.php:887
static userTalkLink( $userId, $userText)
Definition Linker.php:1344
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:1681
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:1543
static normalizeSubpageLink( $contextTitle, $target, &$text)
Definition Linker.php:1584
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:1089
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:1248
static emailLink( $userId, $userText)
Definition Linker.php:1387
static formatHiddenCategories( $hiddencats)
Returns HTML for the "hidden categories on this page" list.
Definition Linker.php:2148
static getInvalidTitleDescription(IContextSource $context, $namespace, $title)
Get a message saying that an invalid title was encountered.
Definition Linker.php:229
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:2011
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition Linker.php:1136
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:1842
static linkKnown( $target, $html=null, $customAttribs=[], $query=[], $options=[ 'known'])
Identical to link(), except $options defaults to 'known'.
Definition Linker.php:172
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:2277
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:1802
static makeHeadline( $level, $attribs, $anchor, $html, $link, $fallbackAnchor=false)
Create a headline for content.
Definition Linker.php:1896
static makeExternalImage( $url, $alt='')
Return the code for images which were added via external links, via Parser::maybeMakeExternalImage().
Definition Linker.php:272
static tooltip( $name, $options=null)
Returns raw bits of HTML, use titleAttrib()
Definition Linker.php:2407
static makeBrokenImageLinkObj( $title, $label='', $query='', $unused1='', $unused2='', $time=false, array $handlerParams=[], bool $currentExists=false)
Make a "broken" link to an image.
Definition Linker.php:924
static makeMediaLinkFile(LinkTarget $title, $file, $html='')
Create a direct link to a given uploaded file.
Definition Linker.php:1047
static accesskey( $name, $localizer=null)
Given the id of an interface element, constructs the appropriate accesskey attribute from the system ...
Definition Linker.php:2247
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:2193
static userToolLinksRedContribs( $userId, $userText, $edits=null, $useParentheses=true)
Alias for userToolLinks( $userId, $userText, true );.
Definition Linker.php:1332
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:1917
static tocIndent()
Add another level to the Table of Contents.
Definition Linker.php:1736
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:2353
static formatRevisionSize( $size)
Definition Linker.php:1721
static tocUnindent( $level)
Finish one or more sublevels on the Table of Contents.
Definition Linker.php:1747
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:1570
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:1190
static revUserLink(RevisionRecord $revRecord, $isPublic=false)
Generate a user link if the current user is allowed to view it.
Definition Linker.php:1413
static getRevisionDeletedClass(RevisionRecord $revisionRecord)
Returns css class of a deleted revision.
Definition Linker.php:1441
static generateRollback(RevisionRecord $revRecord, IContextSource $context=null, $options=[])
Generate a rollback link for a given revision.
Definition Linker.php:1956
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:1461
static tocLine( $linkAnchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition Linker.php:1762
static buildRollbackLink(RevisionRecord $revRecord, IContextSource $context=null, $editCount=false)
Build a raw rollback link, useful for collections of "tool" links.
Definition Linker.php:2077
A class containing constants representing the names of configuration variables.
const UploadNavigationUrl
Name constant for the UploadNavigationUrl setting, for use with Config::get()
const MaxTocLevel
Name constant for the MaxTocLevel 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.
static getInstance()
Returns the global default instance of the top level service locator.
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:76
Class to parse and build external user names.
internal since 1.36
Definition User.php:98
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:144
static numParam( $num)
Definition Message.php:1154
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition Parser.php:115
getTargetLanguage()
Get the target language for the content being parsed.
Definition Parser.php:1125
getTitle()
Definition Parser.php:964
getBadFileLookup()
Get the BadFileLookup instance that this Parser is using.
Definition Parser.php:1199
static getExternalLinkRel( $url=false, LinkTarget $title=null)
Get the rel attribute for a particular external link.
Definition Parser.php:2201
Group all the pieces relevant to the context of a request into one instance.
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 the current execution context, such as a web reque...
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.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42