MediaWiki REL1_40
Linker.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\Linker;
24
25use Config;
29use File;
30use Hooks;
31use HtmlArmor;
33use Language;
43use Message;
45use Parser;
47use SpecialPage;
48use TitleValue;
49use User;
50use WatchedItem;
51use Wikimedia\Assert\Assert;
52use Wikimedia\IPUtils;
53use Wikimedia\Parsoid\Core\TOCData;
55use Wikimedia\RemexHtml\Serializer\SerializerNode;
56use Xml;
57
67class Linker {
71 public const TOOL_LINKS_NOBLOCK = 1;
72 public const TOOL_LINKS_EMAIL = 2;
73
116 public static function link(
117 $target, $html = null, $customAttribs = [], $query = [], $options = []
118 ) {
119 if ( !$target instanceof LinkTarget ) {
120 wfWarn( __METHOD__ . ': Requires $target to be a LinkTarget object.', 2 );
121 return "<!-- ERROR -->$html";
122 }
123
124 $services = MediaWikiServices::getInstance();
125 $options = (array)$options;
126 if ( $options ) {
127 // Custom options, create new LinkRenderer
128 $linkRenderer = $services->getLinkRendererFactory()
129 ->createFromLegacyOptions( $options );
130 } else {
131 $linkRenderer = $services->getLinkRenderer();
132 }
133
134 if ( $html !== null ) {
135 $text = new HtmlArmor( $html );
136 } else {
137 $text = null;
138 }
139
140 if ( in_array( 'known', $options, true ) ) {
141 return $linkRenderer->makeKnownLink( $target, $text, $customAttribs, $query );
142 }
143
144 if ( in_array( 'broken', $options, true ) ) {
145 return $linkRenderer->makeBrokenLink( $target, $text, $customAttribs, $query );
146 }
147
148 if ( in_array( 'noclasses', $options, true ) ) {
149 return $linkRenderer->makePreloadedLink( $target, $text, '', $customAttribs, $query );
150 }
151
152 return $linkRenderer->makeLink( $target, $text, $customAttribs, $query );
153 }
154
168 public static function linkKnown(
169 $target, $html = null, $customAttribs = [],
170 $query = [], $options = [ 'known' ]
171 ) {
172 return self::link( $target, $html, $customAttribs, $query, $options );
173 }
174
192 public static function makeSelfLinkObj( $nt, $html = '', $query = '', $trail = '', $prefix = '', $hash = '' ) {
193 $nt = Title::newFromLinkTarget( $nt );
194 $attrs = [
195 'class' => 'mw-selflink',
196 ];
197 if ( $hash ) {
198 $attrs['href'] = '#' . $hash;
199 $attrs['class'] = 'mw-selflink-fragment';
200 } else {
201 // For backwards compatibility with gadgets we add selflink as well.
202 $attrs['class'] = 'mw-selflink selflink';
203 }
204 $ret = Html::rawElement( 'a', $attrs, $prefix . $html ) . $trail;
205 if ( !Hooks::runner()->onSelfLinkBegin( $nt, $html, $trail, $prefix, $ret ) ) {
206 return $ret;
207 }
208
209 if ( $html == '' ) {
210 $html = htmlspecialchars( $nt->getPrefixedText() );
211 }
212 [ $inside, $trail ] = self::splitTrail( $trail );
213 return Html::rawElement( 'a', $attrs, $prefix . $html . $inside ) . $trail;
214 }
215
226 public static function getInvalidTitleDescription( IContextSource $context, $namespace, $title ) {
227 // First we check whether the namespace exists or not.
228 if ( MediaWikiServices::getInstance()->getNamespaceInfo()->exists( $namespace ) ) {
229 if ( $namespace == NS_MAIN ) {
230 $name = $context->msg( 'blanknamespace' )->text();
231 } else {
232 $name = MediaWikiServices::getInstance()->getContentLanguage()->
233 getFormattedNsText( $namespace );
234 }
235 return $context->msg( 'invalidtitle-knownnamespace', $namespace, $name, $title )->text();
236 }
237
238 return $context->msg( 'invalidtitle-unknownnamespace', $namespace, $title )->text();
239 }
240
249 private static function fnamePart( $url ) {
250 $basename = strrchr( $url, '/' );
251 if ( $basename === false ) {
252 $basename = $url;
253 } else {
254 $basename = substr( $basename, 1 );
255 }
256 return $basename;
257 }
258
269 public static function makeExternalImage( $url, $alt = '' ) {
270 if ( $alt == '' ) {
271 $alt = self::fnamePart( $url );
272 }
273 $img = '';
274 $success = Hooks::runner()->onLinkerMakeExternalImage( $url, $alt, $img );
275 if ( !$success ) {
276 wfDebug( "Hook LinkerMakeExternalImage changed the output of external image "
277 . "with url {$url} and alt text {$alt} to {$img}" );
278 return $img;
279 }
280 return Html::element( 'img',
281 [
282 'src' => $url,
283 'alt' => $alt
284 ]
285 );
286 }
287
326 public static function makeImageLink( Parser $parser, LinkTarget $title,
327 $file, $frameParams = [], $handlerParams = [], $time = false,
328 $query = '', $widthOption = null
329 ) {
331 $res = null;
332 $dummy = new DummyLinker;
333 if ( !Hooks::runner()->onImageBeforeProduceHTML( $dummy, $title,
334 // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
335 $file, $frameParams, $handlerParams, $time, $res,
336 // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
337 $parser, $query, $widthOption )
338 ) {
339 return $res;
340 }
341
342 if ( $file && !$file->allowInlineDisplay() ) {
343 wfDebug( __METHOD__ . ': ' . $title->getPrefixedDBkey() . ' does not allow inline display' );
344 return self::link( $title );
345 }
346
347 // Clean up parameters
348 $page = $handlerParams['page'] ?? false;
349 if ( !isset( $frameParams['align'] ) ) {
350 $frameParams['align'] = '';
351 }
352 if ( !isset( $frameParams['title'] ) ) {
353 $frameParams['title'] = '';
354 }
355 if ( !isset( $frameParams['class'] ) ) {
356 $frameParams['class'] = '';
357 }
358
359 $services = MediaWikiServices::getInstance();
360 $config = $services->getMainConfig();
361 $enableLegacyMediaDOM = $config->get( MainConfigNames::ParserEnableLegacyMediaDOM );
362
363 $classes = [];
364 if (
365 !isset( $handlerParams['width'] ) &&
366 !isset( $frameParams['manualthumb'] ) &&
367 !isset( $frameParams['framed'] )
368 ) {
369 $classes[] = 'mw-default-size';
370 }
371
372 $prefix = $postfix = '';
373
374 if ( $enableLegacyMediaDOM ) {
375 if ( $frameParams['align'] == 'center' ) {
376 $prefix = '<div class="center">';
377 $postfix = '</div>';
378 $frameParams['align'] = 'none';
379 }
380 }
381
382 if ( $file && !isset( $handlerParams['width'] ) ) {
383 if ( isset( $handlerParams['height'] ) && $file->isVectorized() ) {
384 // If its a vector image, and user only specifies height
385 // we don't want it to be limited by its "normal" width.
386 $svgMaxSize = $config->get( MainConfigNames::SVGMaxSize );
387 $handlerParams['width'] = $svgMaxSize;
388 } else {
389 $handlerParams['width'] = $file->getWidth( $page );
390 }
391
392 if ( isset( $frameParams['thumbnail'] )
393 || isset( $frameParams['manualthumb'] )
394 || isset( $frameParams['framed'] )
395 || isset( $frameParams['frameless'] )
396 || !$handlerParams['width']
397 ) {
398 $thumbLimits = $config->get( MainConfigNames::ThumbLimits );
399 $thumbUpright = $config->get( MainConfigNames::ThumbUpright );
400 if ( $widthOption === null || !isset( $thumbLimits[$widthOption] ) ) {
401 $userOptionsLookup = $services->getUserOptionsLookup();
402 $widthOption = $userOptionsLookup->getDefaultOption( 'thumbsize' );
403 }
404
405 // Reduce width for upright images when parameter 'upright' is used
406 if ( isset( $frameParams['upright'] ) && $frameParams['upright'] == 0 ) {
407 $frameParams['upright'] = $thumbUpright;
408 }
409
410 // For caching health: If width scaled down due to upright
411 // parameter, round to full __0 pixel to avoid the creation of a
412 // lot of odd thumbs.
413 $prefWidth = isset( $frameParams['upright'] ) ?
414 round( $thumbLimits[$widthOption] * $frameParams['upright'], -1 ) :
415 $thumbLimits[$widthOption];
416
417 // Use width which is smaller: real image width or user preference width
418 // Unless image is scalable vector.
419 if ( !isset( $handlerParams['height'] ) && ( $handlerParams['width'] <= 0 ||
420 $prefWidth < $handlerParams['width'] || $file->isVectorized() ) ) {
421 $handlerParams['width'] = $prefWidth;
422 }
423 }
424 }
425
426 // Parser::makeImage has a similarly named variable
427 $hasVisibleCaption = isset( $frameParams['thumbnail'] ) ||
428 isset( $frameParams['manualthumb'] ) ||
429 isset( $frameParams['framed'] );
430
431 if ( $hasVisibleCaption ) {
432 if ( $enableLegacyMediaDOM ) {
433 // This is no longer needed in our new media output, since the
434 // default styling in content.media-common.less takes care of it;
435 // see T269704.
436
437 # Create a thumbnail. Alignment depends on the writing direction of
438 # the page content language (right-aligned for LTR languages,
439 # left-aligned for RTL languages)
440 # If a thumbnail width has not been provided, it is set
441 # to the default user option as specified in Language*.php
442 if ( $frameParams['align'] == '' ) {
443 $frameParams['align'] = $parser->getTargetLanguage()->alignEnd();
444 }
445 }
446 return $prefix . self::makeThumbLink2(
447 $title, $file, $frameParams, $handlerParams, $time, $query,
448 $classes, $parser
449 ) . $postfix;
450 }
451
452 $rdfaType = 'mw:File';
453
454 if ( isset( $frameParams['frameless'] ) ) {
455 $rdfaType .= '/Frameless';
456 if ( $file ) {
457 $srcWidth = $file->getWidth( $page );
458 # For "frameless" option: do not present an image bigger than the
459 # source (for bitmap-style images). This is the same behavior as the
460 # "thumb" option does it already.
461 if ( $srcWidth && !$file->mustRender() && $handlerParams['width'] > $srcWidth ) {
462 $handlerParams['width'] = $srcWidth;
463 }
464 }
465 }
466
467 if ( $file && isset( $handlerParams['width'] ) ) {
468 # Create a resized image, without the additional thumbnail features
469 $thumb = $file->transform( $handlerParams );
470 } else {
471 $thumb = false;
472 }
473
474 $isBadFile = $file && $thumb &&
475 $parser->getBadFileLookup()->isBadFile( $title->getDBkey(), $parser->getTitle() );
476
477 if ( !$thumb || ( !$enableLegacyMediaDOM && $thumb->isError() ) || $isBadFile ) {
478 $rdfaType = 'mw:Error ' . $rdfaType;
479 $currentExists = $file && $file->exists();
480 if ( $enableLegacyMediaDOM ) {
481 $label = $frameParams['title'];
482 } else {
483 if ( $currentExists && !$thumb ) {
484 $label = wfMessage( 'thumbnail_error', '' )->text();
485 } elseif ( $thumb && $thumb->isError() ) {
486 Assert::invariant(
487 $thumb instanceof MediaTransformError,
488 'Unknown MediaTransformOutput: ' . get_class( $thumb )
489 );
490 $label = $thumb->toText();
491 } else {
492 $label = $frameParams['alt'] ?? '';
493 }
494 }
496 $title, $label, '', '', '', (bool)$time, $handlerParams, $currentExists
497 );
498 } else {
499 self::processResponsiveImages( $file, $thumb, $handlerParams );
500 $params = [];
501 // An empty alt indicates an image is not a key part of the content
502 // and that non-visual browsers may omit it from rendering. Only
503 // set the parameter if it's explicitly requested.
504 if ( isset( $frameParams['alt'] ) ) {
505 $params['alt'] = $frameParams['alt'];
506 }
507 $params['title'] = $frameParams['title'];
508 if ( $enableLegacyMediaDOM ) {
509 $params += [
510 'valign' => $frameParams['valign'] ?? false,
511 'img-class' => $frameParams['class'],
512 ];
513 if ( isset( $frameParams['border'] ) ) {
514 $params['img-class'] .= ( $params['img-class'] !== '' ? ' ' : '' ) . 'thumbborder';
515 }
516 }
517 $params = self::getImageLinkMTOParams( $frameParams, $query, $parser ) + $params;
518 $s = $thumb->toHtml( $params );
519 }
520
521 if ( $enableLegacyMediaDOM ) {
522 if ( $frameParams['align'] != '' ) {
523 $s = Html::rawElement(
524 'div',
525 [ 'class' => 'float' . $frameParams['align'] ],
526 $s
527 );
528 }
529 return str_replace( "\n", ' ', $prefix . $s . $postfix );
530 }
531
532 $wrapper = 'span';
533 $caption = '';
534
535 if ( $frameParams['align'] != '' ) {
536 $wrapper = 'figure';
537 // Possible values: mw-halign-left mw-halign-center mw-halign-right mw-halign-none
538 $classes[] = "mw-halign-{$frameParams['align']}";
539 $caption = Html::rawElement(
540 'figcaption', [], $frameParams['caption'] ?? ''
541 );
542 } elseif ( isset( $frameParams['valign'] ) ) {
543 // Possible values: mw-valign-middle mw-valign-baseline mw-valign-sub
544 // mw-valign-super mw-valign-top mw-valign-text-top mw-valign-bottom
545 // mw-valign-text-bottom
546 $classes[] = "mw-valign-{$frameParams['valign']}";
547 }
548
549 if ( isset( $frameParams['border'] ) ) {
550 $classes[] = 'mw-image-border';
551 }
552
553 if ( isset( $frameParams['class'] ) ) {
554 $classes[] = $frameParams['class'];
555 }
556
557 $attribs = [
558 'class' => $classes,
559 'typeof' => $rdfaType,
560 ];
561
562 $s = Html::rawElement( $wrapper, $attribs, $s . $caption );
563
564 return str_replace( "\n", ' ', $s );
565 }
566
575 public static function getImageLinkMTOParams( $frameParams, $query = '', $parser = null ) {
576 $mtoParams = [];
577 if ( isset( $frameParams['link-url'] ) && $frameParams['link-url'] !== '' ) {
578 $mtoParams['custom-url-link'] = $frameParams['link-url'];
579 if ( isset( $frameParams['link-target'] ) ) {
580 $mtoParams['custom-target-link'] = $frameParams['link-target'];
581 }
582 if ( $parser ) {
583 $extLinkAttrs = $parser->getExternalLinkAttribs( $frameParams['link-url'] );
584 foreach ( $extLinkAttrs as $name => $val ) {
585 // Currently could include 'rel' and 'target'
586 $mtoParams['parser-extlink-' . $name] = $val;
587 }
588 }
589 } elseif ( isset( $frameParams['link-title'] ) && $frameParams['link-title'] !== '' ) {
590 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
591 $mtoParams['custom-title-link'] = Title::newFromLinkTarget(
592 $linkRenderer->normalizeTarget( $frameParams['link-title'] )
593 );
594 if ( isset( $frameParams['link-title-query'] ) ) {
595 $mtoParams['custom-title-link-query'] = $frameParams['link-title-query'];
596 }
597 } elseif ( !empty( $frameParams['no-link'] ) ) {
598 // No link
599 } else {
600 $mtoParams['desc-link'] = true;
601 $mtoParams['desc-query'] = $query;
602 }
603 return $mtoParams;
604 }
605
618 public static function makeThumbLinkObj(
619 LinkTarget $title, $file, $label = '', $alt = '', $align = null,
620 $params = [], $framed = false, $manualthumb = ''
621 ) {
622 $frameParams = [
623 'alt' => $alt,
624 'caption' => $label,
625 'align' => $align
626 ];
627 $classes = [];
628 if ( $manualthumb ) {
629 $frameParams['manualthumb'] = $manualthumb;
630 } elseif ( $framed ) {
631 $frameParams['framed'] = true;
632 } elseif ( !isset( $params['width'] ) ) {
633 $classes[] = 'mw-default-size';
634 }
636 $title, $file, $frameParams, $params, false, '', $classes
637 );
638 }
639
651 public static function makeThumbLink2(
652 LinkTarget $title, $file, $frameParams = [], $handlerParams = [],
653 $time = false, $query = '', array $classes = [], ?Parser $parser = null
654 ) {
655 $exists = $file && $file->exists();
656
657 $services = MediaWikiServices::getInstance();
658 $enableLegacyMediaDOM = $services->getMainConfig()->get( MainConfigNames::ParserEnableLegacyMediaDOM );
659
660 $page = $handlerParams['page'] ?? false;
661 if ( !isset( $frameParams['align'] ) ) {
662 $frameParams['align'] = '';
663 if ( $enableLegacyMediaDOM ) {
664 $frameParams['align'] = 'right';
665 }
666 }
667 if ( !isset( $frameParams['caption'] ) ) {
668 $frameParams['caption'] = '';
669 }
670
671 if ( empty( $handlerParams['width'] ) ) {
672 // Reduce width for upright images when parameter 'upright' is used
673 $handlerParams['width'] = isset( $frameParams['upright'] ) ? 130 : 180;
674 }
675
676 $thumb = false;
677 $noscale = false;
678 $manualthumb = false;
679 $manual_title = '';
680 $rdfaType = 'mw:File/Thumb';
681
682 if ( !$exists ) {
683 // Same precedence as the $exists case
684 if ( !isset( $frameParams['manualthumb'] ) && isset( $frameParams['framed'] ) ) {
685 $rdfaType = 'mw:File/Frame';
686 }
687 $outerWidth = $handlerParams['width'] + 2;
688 } else {
689 if ( isset( $frameParams['manualthumb'] ) ) {
690 # Use manually specified thumbnail
691 $manual_title = Title::makeTitleSafe( NS_FILE, $frameParams['manualthumb'] );
692 if ( $manual_title ) {
693 $manual_img = $services->getRepoGroup()
694 ->findFile( $manual_title );
695 if ( $manual_img ) {
696 $thumb = $manual_img->getUnscaledThumb( $handlerParams );
697 $manualthumb = true;
698 }
699 }
700 } elseif ( isset( $frameParams['framed'] ) ) {
701 // Use image dimensions, don't scale
702 $thumb = $file->getUnscaledThumb( $handlerParams );
703 $noscale = true;
704 $rdfaType = 'mw:File/Frame';
705 } else {
706 # Do not present an image bigger than the source, for bitmap-style images
707 # This is a hack to maintain compatibility with arbitrary pre-1.10 behavior
708 $srcWidth = $file->getWidth( $page );
709 if ( $srcWidth && !$file->mustRender() && $handlerParams['width'] > $srcWidth ) {
710 $handlerParams['width'] = $srcWidth;
711 }
712 $thumb = $file->transform( $handlerParams );
713 }
714
715 if ( $thumb ) {
716 $outerWidth = $thumb->getWidth() + 2;
717 } else {
718 $outerWidth = $handlerParams['width'] + 2;
719 }
720 }
721
722 $url = Title::newFromLinkTarget( $title )->getLocalURL( $query );
723 $linkTitleQuery = [];
724
725 if ( $page ) {
726 $linkTitleQuery['page'] = $page;
727 # ThumbnailImage::toHtml() already adds page= onto the end of DjVu URLs
728 # So we don't need to pass it here in $query. However, the URL for the
729 # zoom icon still needs it, so we make a unique query for it. See T16771
730 # FIXME: What about "lang" and other querystring parameters
731 $url = wfAppendQuery( $url, $linkTitleQuery );
732 }
733
734 if ( $manualthumb
735 && !isset( $frameParams['link-title'] )
736 && !isset( $frameParams['link-url'] )
737 && !isset( $frameParams['no-link'] ) ) {
738 $frameParams['link-title'] = $title;
739 $frameParams['link-title-query'] = $linkTitleQuery;
740 }
741
742 if ( $frameParams['align'] != '' ) {
743 // Possible values: mw-halign-left mw-halign-center mw-halign-right mw-halign-none
744 $classes[] = "mw-halign-{$frameParams['align']}";
745 }
746
747 if ( isset( $frameParams['class'] ) ) {
748 $classes[] = $frameParams['class'];
749 }
750
751 $s = '';
752
753 if ( $enableLegacyMediaDOM ) {
754 $s .= "<div class=\"thumb t{$frameParams['align']}\">"
755 . "<div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
756 }
757
758 $isBadFile = $exists && $thumb && $parser &&
759 $parser->getBadFileLookup()->isBadFile(
760 $manualthumb ? $manual_title : $title->getDBkey(),
761 $parser->getTitle()
762 );
763
764 if ( !$exists ) {
765 $rdfaType = 'mw:Error ' . $rdfaType;
766 $label = '';
767 if ( !$enableLegacyMediaDOM ) {
768 $label = $frameParams['alt'] ?? '';
769 }
771 $title, $label, '', '', '', (bool)$time, $handlerParams, false
772 );
773 $zoomIcon = '';
774 } elseif ( !$thumb || ( !$enableLegacyMediaDOM && $thumb->isError() ) || $isBadFile ) {
775 $rdfaType = 'mw:Error ' . $rdfaType;
776 if ( $enableLegacyMediaDOM ) {
777 if ( !$thumb ) {
778 $s .= wfMessage( 'thumbnail_error', '' )->escaped();
779 } else {
781 $title, '', '', '', '', (bool)$time, $handlerParams, true
782 );
783 }
784 } else {
785 if ( $thumb && $thumb->isError() ) {
786 Assert::invariant(
787 $thumb instanceof MediaTransformError,
788 'Unknown MediaTransformOutput: ' . get_class( $thumb )
789 );
790 $label = $thumb->toText();
791 } elseif ( !$thumb ) {
792 $label = wfMessage( 'thumbnail_error', '' )->text();
793 } else {
794 $label = '';
795 }
797 $title, $label, '', '', '', (bool)$time, $handlerParams, true
798 );
799 }
800 $zoomIcon = '';
801 } else {
802 if ( !$noscale && !$manualthumb ) {
803 self::processResponsiveImages( $file, $thumb, $handlerParams );
804 }
805 $params = [];
806 // An empty alt indicates an image is not a key part of the content
807 // and that non-visual browsers may omit it from rendering. Only
808 // set the parameter if it's explicitly requested.
809 if ( isset( $frameParams['alt'] ) ) {
810 $params['alt'] = $frameParams['alt'];
811 }
812 if ( $enableLegacyMediaDOM ) {
813 $params += [
814 'img-class' => ( isset( $frameParams['class'] ) && $frameParams['class'] !== ''
815 ? $frameParams['class'] . ' '
816 : '' ) . 'thumbimage'
817 ];
818 }
819 $params = self::getImageLinkMTOParams( $frameParams, $query, $parser ) + $params;
820 $s .= $thumb->toHtml( $params );
821 if ( isset( $frameParams['framed'] ) ) {
822 $zoomIcon = '';
823 } else {
824 $zoomIcon = Html::rawElement( 'div', [ 'class' => 'magnify' ],
825 Html::rawElement( 'a', [
826 'href' => $url,
827 'class' => 'internal',
828 'title' => wfMessage( 'thumbnail-more' )->text(),
829 ] )
830 );
831 }
832 }
833
834 if ( $enableLegacyMediaDOM ) {
835 $s .= ' <div class="thumbcaption">' . $zoomIcon . $frameParams['caption'] . '</div></div></div>';
836 return str_replace( "\n", ' ', $s );
837 }
838
839 $s .= Html::rawElement(
840 'figcaption', [], $frameParams['caption'] ?? ''
841 );
842
843 $attribs = [
844 'class' => $classes,
845 'typeof' => $rdfaType,
846 ];
847
848 $s = Html::rawElement( 'figure', $attribs, $s );
849
850 return str_replace( "\n", ' ', $s );
851 }
852
861 public static function processResponsiveImages( $file, $thumb, $hp ) {
862 $responsiveImages = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::ResponsiveImages );
863 if ( $responsiveImages && $thumb && !$thumb->isError() ) {
864 $hp15 = $hp;
865 $hp15['width'] = round( $hp['width'] * 1.5 );
866 $hp20 = $hp;
867 $hp20['width'] = $hp['width'] * 2;
868 if ( isset( $hp['height'] ) ) {
869 $hp15['height'] = round( $hp['height'] * 1.5 );
870 $hp20['height'] = $hp['height'] * 2;
871 }
872
873 $thumb15 = $file->transform( $hp15 );
874 $thumb20 = $file->transform( $hp20 );
875 if ( $thumb15 && !$thumb15->isError() && $thumb15->getUrl() !== $thumb->getUrl() ) {
876 $thumb->responsiveUrls['1.5'] = $thumb15->getUrl();
877 }
878 if ( $thumb20 && !$thumb20->isError() && $thumb20->getUrl() !== $thumb->getUrl() ) {
879 $thumb->responsiveUrls['2'] = $thumb20->getUrl();
880 }
881 }
882 }
883
898 public static function makeBrokenImageLinkObj(
899 $title, $label = '', $query = '', $unused1 = '', $unused2 = '',
900 $time = false, array $handlerParams = [], bool $currentExists = false
901 ) {
902 if ( !$title instanceof LinkTarget ) {
903 wfWarn( __METHOD__ . ': Requires $title to be a LinkTarget object.' );
904 return "<!-- ERROR -->" . htmlspecialchars( $label );
905 }
906
908 $services = MediaWikiServices::getInstance();
909 $mainConfig = $services->getMainConfig();
910 $enableUploads = $mainConfig->get( MainConfigNames::EnableUploads );
911 $uploadMissingFileUrl = $mainConfig->get( MainConfigNames::UploadMissingFileUrl );
912 $uploadNavigationUrl = $mainConfig->get( MainConfigNames::UploadNavigationUrl );
913 if ( $label == '' ) {
914 $label = $title->getPrefixedText();
915 }
916
917 $html = Html::element( 'span', [
918 'class' => 'mw-broken-media',
919 // These data attributes are used to dynamically size the span, see T273013
920 'data-width' => $handlerParams['width'] ?? null,
921 'data-height' => $handlerParams['height'] ?? null,
922 ], $label );
923
924 if ( $mainConfig->get( MainConfigNames::ParserEnableLegacyMediaDOM ) ) {
925 $html = htmlspecialchars( $label, ENT_COMPAT );
926 }
927
928 $repoGroup = $services->getRepoGroup();
929 $currentExists = $currentExists ||
930 ( $time && $repoGroup->findFile( $title ) !== false );
931
932 if ( ( $uploadMissingFileUrl || $uploadNavigationUrl || $enableUploads )
933 && !$currentExists
934 ) {
935 if (
936 $title->inNamespace( NS_FILE ) &&
937 $repoGroup->getLocalRepo()->checkRedirect( $title )
938 ) {
939 // We already know it's a redirect, so mark it accordingly
940 return self::link(
941 $title,
942 $html,
943 [ 'class' => 'mw-redirect' ],
944 wfCgiToArray( $query ),
945 [ 'known', 'noclasses' ]
946 );
947 }
948 return Html::rawElement( 'a', [
949 'href' => self::getUploadUrl( $title, $query ),
950 'class' => 'new',
951 'title' => $title->getPrefixedText()
952 ], $html );
953 }
954 return self::link(
955 $title,
956 $html,
957 [],
958 wfCgiToArray( $query ),
959 [ 'known', 'noclasses' ]
960 );
961 }
962
971 protected static function getUploadUrl( $destFile, $query = '' ) {
972 $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
973 $uploadMissingFileUrl = $mainConfig->get( MainConfigNames::UploadMissingFileUrl );
974 $uploadNavigationUrl = $mainConfig->get( MainConfigNames::UploadNavigationUrl );
975 $q = 'wpDestFile=' . Title::castFromLinkTarget( $destFile )->getPartialURL();
976 if ( $query != '' ) {
977 $q .= '&' . $query;
978 }
979
980 if ( $uploadMissingFileUrl ) {
981 return wfAppendQuery( $uploadMissingFileUrl, $q );
982 }
983
984 if ( $uploadNavigationUrl ) {
985 return wfAppendQuery( $uploadNavigationUrl, $q );
986 }
987
988 $upload = SpecialPage::getTitleFor( 'Upload' );
989
990 return $upload->getLocalURL( $q );
991 }
992
1002 public static function makeMediaLinkObj( $title, $html = '', $time = false ) {
1003 $img = MediaWikiServices::getInstance()->getRepoGroup()->findFile(
1004 $title, [ 'time' => $time ]
1005 );
1006 return self::makeMediaLinkFile( $title, $img, $html );
1007 }
1008
1021 public static function makeMediaLinkFile( LinkTarget $title, $file, $html = '' ) {
1022 if ( $file && $file->exists() ) {
1023 $url = $file->getUrl();
1024 $class = 'internal';
1025 } else {
1026 $url = self::getUploadUrl( $title );
1027 $class = 'new';
1028 }
1029
1030 $alt = $title->getText();
1031 if ( $html == '' ) {
1032 $html = $alt;
1033 }
1034
1035 $ret = '';
1036 $attribs = [
1037 'href' => $url,
1038 'class' => $class,
1039 'title' => $alt
1040 ];
1041
1042 if ( !Hooks::runner()->onLinkerMakeMediaLinkFile(
1043 Title::castFromLinkTarget( $title ), $file, $html, $attribs, $ret )
1044 ) {
1045 wfDebug( "Hook LinkerMakeMediaLinkFile changed the output of link "
1046 . "with url {$url} and text {$html} to {$ret}" );
1047 return $ret;
1048 }
1049
1050 return Html::rawElement( 'a', $attribs, $html );
1051 }
1052
1063 public static function specialLink( $name, $key = '' ) {
1064 $queryPos = strpos( $name, '?' );
1065 if ( $queryPos !== false ) {
1066 $getParams = wfCgiToArray( substr( $name, $queryPos + 1 ) );
1067 $name = substr( $name, 0, $queryPos );
1068 } else {
1069 $getParams = [];
1070 }
1071
1072 $slashPos = strpos( $name, '/' );
1073 if ( $slashPos !== false ) {
1074 $subpage = substr( $name, $slashPos + 1 );
1075 $name = substr( $name, 0, $slashPos );
1076 } else {
1077 $subpage = false;
1078 }
1079
1080 if ( $key == '' ) {
1081 $key = strtolower( $name );
1082 }
1083
1084 return self::linkKnown(
1085 SpecialPage::getTitleFor( $name, $subpage ),
1086 wfMessage( $key )->escaped(),
1087 [],
1088 $getParams
1089 );
1090 }
1091
1110 public static function makeExternalLink( $url, $text, $escape = true,
1111 $linktype = '', $attribs = [], $title = null
1112 ) {
1113 global $wgTitle;
1114 $class = 'external';
1115 if ( $linktype ) {
1116 $class .= " $linktype";
1117 }
1118 if ( isset( $attribs['class'] ) && $attribs['class'] ) {
1119 $class .= " {$attribs['class']}";
1120 }
1121 $attribs['class'] = $class;
1122
1123 if ( $escape ) {
1124 $text = htmlspecialchars( $text, ENT_COMPAT );
1125 }
1126
1127 if ( !$title ) {
1128 $title = $wgTitle;
1129 }
1130 $newRel = Parser::getExternalLinkRel( $url, $title );
1131 if ( !isset( $attribs['rel'] ) || $attribs['rel'] === '' ) {
1132 $attribs['rel'] = $newRel;
1133 } elseif ( $newRel !== null ) {
1134 // Merge the rel attributes.
1135 $newRels = explode( ' ', $newRel );
1136 $oldRels = explode( ' ', $attribs['rel'] );
1137 $combined = array_unique( array_merge( $newRels, $oldRels ) );
1138 $attribs['rel'] = implode( ' ', $combined );
1139 }
1140 $link = '';
1141 $success = Hooks::runner()->onLinkerMakeExternalLink(
1142 $url, $text, $link, $attribs, $linktype );
1143 if ( !$success ) {
1144 wfDebug( "Hook LinkerMakeExternalLink changed the output of link "
1145 . "with url {$url} and text {$text} to {$link}" );
1146 return $link;
1147 }
1148 $attribs['href'] = $url;
1149 return Html::rawElement( 'a', $attribs, $text );
1150 }
1151
1164 public static function userLink(
1165 $userId,
1166 $userName,
1167 $altUserName = false,
1168 $attributes = []
1169 ) {
1170 if ( $userName === '' || $userName === false || $userName === null ) {
1171 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1172 'that need to be fixed?' );
1173 return wfMessage( 'empty-username' )->parse();
1174 }
1175
1176 $classes = 'mw-userlink';
1177 if ( $userId == 0 ) {
1178 $page = ExternalUserNames::getUserLinkTitle( $userName );
1179
1180 if ( ExternalUserNames::isExternal( $userName ) ) {
1181 $classes .= ' mw-extuserlink';
1182 } elseif ( $altUserName === false ) {
1183 $altUserName = IPUtils::prettifyIP( $userName );
1184 }
1185 $classes .= ' mw-anonuserlink'; // Separate link class for anons (T45179)
1186 } else {
1187 $services = MediaWikiServices::getInstance();
1188 if (
1189 $services->getTempUserConfig()->isReservedName( $userName )
1190 ) {
1191 $classes .= ' mw-tempuserlink';
1192 $page = SpecialPage::getTitleFor( 'Contributions', $userName );
1193 } else {
1194 $page = TitleValue::tryNew( NS_USER, strtr( $userName, ' ', '_' ) );
1195 }
1196 }
1197
1198 // Wrap the output with <bdi> tags for directionality isolation
1199 $linkText =
1200 '<bdi>' . htmlspecialchars( $altUserName !== false ? $altUserName : $userName ) . '</bdi>';
1201
1202 if ( isset( $attributes['class'] ) ) {
1203 $attributes['class'] .= ' ' . $classes;
1204 } else {
1205 $attributes['class'] = $classes;
1206 }
1207
1208 return $page
1209 ? self::link( $page, $linkText, $attributes )
1210 : Html::rawElement( 'span', $attributes, $linkText );
1211 }
1212
1227 public static function userToolLinks(
1228 $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits = null,
1229 $useParentheses = true
1230 ) {
1231 if ( $userText === '' ) {
1232 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1233 'that need to be fixed?' );
1234 return ' ' . wfMessage( 'empty-username' )->parse();
1235 }
1236 global $wgLang;
1237 $disableAnonTalk = MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::DisableAnonTalk );
1238 $talkable = !( $disableAnonTalk && $userId == 0 );
1239 $blockable = !( $flags & self::TOOL_LINKS_NOBLOCK );
1240 $addEmailLink = $flags & self::TOOL_LINKS_EMAIL && $userId;
1241
1242 if ( $userId == 0 && ExternalUserNames::isExternal( $userText ) ) {
1243 // No tools for an external user
1244 return '';
1245 }
1246
1247 $items = [];
1248 if ( $talkable ) {
1249 $items[] = self::userTalkLink( $userId, $userText );
1250 }
1251 if ( $userId ) {
1252 // check if the user has an edit
1253 $attribs = [];
1254 $attribs['class'] = 'mw-usertoollinks-contribs';
1255 if ( $redContribsWhenNoEdits ) {
1256 if ( intval( $edits ) === 0 && $edits !== 0 ) {
1257 $user = User::newFromId( $userId );
1258 $edits = $user->getEditCount();
1259 }
1260 if ( $edits === 0 ) {
1261 // Note: "new" class is inappropriate here, as "new" class
1262 // should only be used for pages that do not exist.
1263 $attribs['class'] .= ' mw-usertoollinks-contribs-no-edits';
1264 }
1265 }
1266 $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText );
1267
1268 $items[] = self::link( $contribsPage, wfMessage( 'contribslink' )->escaped(), $attribs );
1269 }
1270 $userCanBlock = RequestContext::getMain()->getAuthority()->isAllowed( 'block' );
1271 if ( $blockable && $userCanBlock ) {
1272 $items[] = self::blockLink( $userId, $userText );
1273 }
1274
1275 $user = RequestContext::getMain()->getUser();
1276 if ( $addEmailLink && $user->canSendEmail() ) {
1277 $items[] = self::emailLink( $userId, $userText );
1278 }
1279
1280 Hooks::runner()->onUserToolLinksEdit( $userId, $userText, $items );
1281
1282 if ( !$items ) {
1283 return '';
1284 }
1285
1286 if ( $useParentheses ) {
1287 return wfMessage( 'word-separator' )->escaped()
1288 . '<span class="mw-usertoollinks">'
1289 . wfMessage( 'parentheses' )->rawParams( $wgLang->pipeList( $items ) )->escaped()
1290 . '</span>';
1291 }
1292
1293 $tools = [];
1294 foreach ( $items as $tool ) {
1295 $tools[] = Html::rawElement( 'span', [], $tool );
1296 }
1297 return ' <span class="mw-usertoollinks mw-changeslist-links">' .
1298 implode( ' ', $tools ) . '</span>';
1299 }
1300
1310 public static function userToolLinksRedContribs(
1311 $userId, $userText, $edits = null, $useParentheses = true
1312 ) {
1313 return self::userToolLinks( $userId, $userText, true, 0, $edits, $useParentheses );
1314 }
1315
1322 public static function userTalkLink( $userId, $userText ) {
1323 if ( $userText === '' ) {
1324 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1325 'that need to be fixed?' );
1326 return wfMessage( 'empty-username' )->parse();
1327 }
1328
1329 $userTalkPage = TitleValue::tryNew( NS_USER_TALK, strtr( $userText, ' ', '_' ) );
1330 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-talk' ];
1331 $linkText = wfMessage( 'talkpagelinktext' )->escaped();
1332
1333 return $userTalkPage
1334 ? self::link( $userTalkPage, $linkText, $moreLinkAttribs )
1335 : Html::rawElement( 'span', $moreLinkAttribs, $linkText );
1336 }
1337
1344 public static function blockLink( $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 $blockPage = SpecialPage::getTitleFor( 'Block', $userText );
1352 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-block' ];
1353
1354 return self::link( $blockPage,
1355 wfMessage( 'blocklink' )->escaped(),
1356 $moreLinkAttribs
1357 );
1358 }
1359
1365 public static function emailLink( $userId, $userText ) {
1366 if ( $userText === '' ) {
1367 wfLogWarning( __METHOD__ . ' received an empty username. Are there database errors ' .
1368 'that need to be fixed?' );
1369 return wfMessage( 'empty-username' )->parse();
1370 }
1371
1372 $emailPage = SpecialPage::getTitleFor( 'Emailuser', $userText );
1373 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-mail' ];
1374 return self::link( $emailPage,
1375 wfMessage( 'emaillink' )->escaped(),
1376 $moreLinkAttribs
1377 );
1378 }
1379
1391 public static function revUserLink( RevisionRecord $revRecord, $isPublic = false ) {
1392 // TODO inject authority
1393 $authority = RequestContext::getMain()->getAuthority();
1394
1395 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) && $isPublic ) {
1396 $link = wfMessage( 'rev-deleted-user' )->escaped();
1397 } elseif ( $revRecord->userCan( RevisionRecord::DELETED_USER, $authority ) ) {
1398 $revUser = $revRecord->getUser( RevisionRecord::FOR_THIS_USER, $authority );
1399 $link = self::userLink(
1400 $revUser ? $revUser->getId() : 0,
1401 $revUser ? $revUser->getName() : ''
1402 );
1403 } else {
1404 $link = wfMessage( 'rev-deleted-user' )->escaped();
1405 }
1406 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
1407 $class = self::getRevisionDeletedClass( $revRecord );
1408 return '<span class="' . $class . '">' . $link . '</span>';
1409 }
1410 return $link;
1411 }
1412
1419 public static function getRevisionDeletedClass( RevisionRecord $revisionRecord ): string {
1420 $class = 'history-deleted';
1421 if ( $revisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
1422 $class .= ' mw-history-suppressed';
1423 }
1424 return $class;
1425 }
1426
1439 public static function revUserTools(
1440 RevisionRecord $revRecord,
1441 $isPublic = false,
1442 $useParentheses = true
1443 ) {
1444 // TODO inject authority
1445 $authority = RequestContext::getMain()->getAuthority();
1446
1447 if ( $revRecord->userCan( RevisionRecord::DELETED_USER, $authority ) &&
1448 ( !$revRecord->isDeleted( RevisionRecord::DELETED_USER ) || !$isPublic )
1449 ) {
1450 $revUser = $revRecord->getUser( RevisionRecord::FOR_THIS_USER, $authority );
1451 $userId = $revUser ? $revUser->getId() : 0;
1452 $userText = $revUser ? $revUser->getName() : '';
1453
1454 if ( $userId || $userText !== '' ) {
1455 $link = self::userLink(
1456 $userId,
1457 $userText,
1458 false,
1459 [ 'data-mw-revid' => $revRecord->getId() ]
1460 ) . self::userToolLinks(
1461 $userId,
1462 $userText,
1463 false,
1464 0,
1465 null,
1466 $useParentheses
1467 );
1468 }
1469 }
1470
1471 if ( !isset( $link ) ) {
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 . ' mw-userlink">' . $link . '</span>';
1478 }
1479 return $link;
1480 }
1481
1492 public static function expandLocalLinks( string $html ) {
1493 return HtmlHelper::modifyElements(
1494 $html,
1495 static function ( SerializerNode $node ): bool {
1496 return $node->name === 'a' && isset( $node->attrs['href'] );
1497 },
1498 static function ( SerializerNode $node ): SerializerNode {
1499 $node->attrs['href'] =
1500 wfExpandUrl( $node->attrs['href'], PROTO_RELATIVE );
1501 return $node;
1502 }
1503 );
1504 }
1505
1526 public static function formatComment(
1527 $comment, $title = null, $local = false, $wikiId = null
1528 ) {
1529 $formatter = MediaWikiServices::getInstance()->getCommentFormatter();
1530 return $formatter->format( $comment, $title, $local, $wikiId );
1531 }
1532
1552 public static function formatLinksInComment(
1553 $comment, $title = null, $local = false, $wikiId = null
1554 ) {
1555 $formatter = MediaWikiServices::getInstance()->getCommentFormatter();
1556 return $formatter->formatLinksUnsafe( $comment, $title, $local, $wikiId );
1557 }
1558
1565 public static function normalizeSubpageLink( $contextTitle, $target, &$text ) {
1566 # Valid link forms:
1567 # Foobar -- normal
1568 # :Foobar -- override special treatment of prefix (images, language links)
1569 # /Foobar -- convert to CurrentPage/Foobar
1570 # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial and final / from text
1571 # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage
1572 # ../Foobar -- convert to CurrentPage/Foobar,
1573 # (from CurrentPage/CurrentSubPage)
1574 # ../Foobar/ -- convert to CurrentPage/Foobar, use 'Foobar' as text
1575 # (from CurrentPage/CurrentSubPage)
1576
1577 $ret = $target; # default return value is no change
1578
1579 # Some namespaces don't allow subpages,
1580 # so only perform processing if subpages are allowed
1581 if (
1582 $contextTitle && MediaWikiServices::getInstance()->getNamespaceInfo()->
1583 hasSubpages( $contextTitle->getNamespace() )
1584 ) {
1585 $hash = strpos( $target, '#' );
1586 if ( $hash !== false ) {
1587 $suffix = substr( $target, $hash );
1588 $target = substr( $target, 0, $hash );
1589 } else {
1590 $suffix = '';
1591 }
1592 # T9425
1593 $target = trim( $target );
1594 $contextPrefixedText = MediaWikiServices::getInstance()->getTitleFormatter()->
1595 getPrefixedText( $contextTitle );
1596 # Look at the first character
1597 if ( $target != '' && $target[0] === '/' ) {
1598 # / at end means we don't want the slash to be shown
1599 $m = [];
1600 $trailingSlashes = preg_match_all( '%(/+)$%', $target, $m );
1601 if ( $trailingSlashes ) {
1602 $noslash = $target = substr( $target, 1, -strlen( $m[0][0] ) );
1603 } else {
1604 $noslash = substr( $target, 1 );
1605 }
1606
1607 $ret = $contextPrefixedText . '/' . trim( $noslash ) . $suffix;
1608 if ( $text === '' ) {
1609 $text = $target . $suffix;
1610 } # this might be changed for ugliness reasons
1611 } else {
1612 # check for .. subpage backlinks
1613 $dotdotcount = 0;
1614 $nodotdot = $target;
1615 while ( str_starts_with( $nodotdot, '../' ) ) {
1616 ++$dotdotcount;
1617 $nodotdot = substr( $nodotdot, 3 );
1618 }
1619 if ( $dotdotcount > 0 ) {
1620 $exploded = explode( '/', $contextPrefixedText );
1621 if ( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page
1622 $ret = implode( '/', array_slice( $exploded, 0, -$dotdotcount ) );
1623 # / at the end means don't show full path
1624 if ( substr( $nodotdot, -1, 1 ) === '/' ) {
1625 $nodotdot = rtrim( $nodotdot, '/' );
1626 if ( $text === '' ) {
1627 $text = $nodotdot . $suffix;
1628 }
1629 }
1630 $nodotdot = trim( $nodotdot );
1631 if ( $nodotdot != '' ) {
1632 $ret .= '/' . $nodotdot;
1633 }
1634 $ret .= $suffix;
1635 }
1636 }
1637 }
1638 }
1639
1640 return $ret;
1641 }
1642
1662 public static function commentBlock(
1663 $comment, $title = null, $local = false, $wikiId = null, $useParentheses = true
1664 ) {
1665 return MediaWikiServices::getInstance()->getCommentFormatter()
1666 ->formatBlock( $comment, $title, $local, $wikiId, $useParentheses );
1667 }
1668
1684 public static function revComment(
1685 RevisionRecord $revRecord,
1686 $local = false,
1687 $isPublic = false,
1688 $useParentheses = true
1689 ) {
1690 $authority = RequestContext::getMain()->getAuthority();
1691 $formatter = MediaWikiServices::getInstance()->getCommentFormatter();
1692 return $formatter->formatRevision( $revRecord, $authority, $local, $isPublic, $useParentheses );
1693 }
1694
1700 public static function formatRevisionSize( $size ) {
1701 if ( $size == 0 ) {
1702 $stxt = wfMessage( 'historyempty' )->escaped();
1703 } else {
1704 $stxt = wfMessage( 'nbytes' )->numParams( $size )->escaped();
1705 }
1706 return "<span class=\"history-size mw-diff-bytes\" data-mw-bytes=\"$size\">$stxt</span>";
1707 }
1708
1715 public static function tocIndent() {
1716 return "\n<ul>\n";
1717 }
1718
1726 public static function tocUnindent( $level ) {
1727 return "</li>\n" . str_repeat( "</ul>\n</li>\n", $level > 0 ? $level : 0 );
1728 }
1729
1741 public static function tocLine( $linkAnchor, $tocline, $tocnumber, $level, $sectionIndex = false ) {
1742 $classes = "toclevel-$level";
1743
1744 // Parser.php used to suppress tocLine by setting $sectionindex to false.
1745 // In those circumstances, we can now encounter '' or a "T-" prefixed index
1746 // for when the section comes from templates.
1747 if ( $sectionIndex !== false && $sectionIndex !== '' && !str_starts_with( $sectionIndex, "T-" ) ) {
1748 $classes .= " tocsection-$sectionIndex";
1749 }
1750
1751 // <li class="$classes"><a href="#$linkAnchor"><span class="tocnumber">
1752 // $tocnumber</span> <span class="toctext">$tocline</span></a>
1753 return Html::openElement( 'li', [ 'class' => $classes ] )
1754 . Html::rawElement( 'a',
1755 [ 'href' => "#$linkAnchor" ],
1756 Html::element( 'span', [ 'class' => 'tocnumber' ], $tocnumber )
1757 . ' '
1758 . Html::rawElement( 'span', [ 'class' => 'toctext' ], $tocline )
1759 );
1760 }
1761
1769 public static function tocLineEnd() {
1770 return "</li>\n";
1771 }
1772
1781 public static function tocList( $toc, Language $lang = null ) {
1782 $lang ??= RequestContext::getMain()->getLanguage();
1783
1784 $title = wfMessage( 'toc' )->inLanguage( $lang )->escaped();
1785
1786 return '<div id="toc" class="toc" role="navigation" aria-labelledby="mw-toc-heading">'
1787 . Html::element( 'input', [
1788 'type' => 'checkbox',
1789 'role' => 'button',
1790 'id' => 'toctogglecheckbox',
1791 'class' => 'toctogglecheckbox',
1792 'style' => 'display:none',
1793 ] )
1794 . Html::openElement( 'div', [
1795 'class' => 'toctitle',
1796 'lang' => $lang->getHtmlCode(),
1797 'dir' => $lang->getDir(),
1798 ] )
1799 . '<h2 id="mw-toc-heading">' . $title . '</h2>'
1800 . '<span class="toctogglespan">'
1801 . Html::label( '', 'toctogglecheckbox', [
1802 'class' => 'toctogglelabel',
1803 ] )
1804 . '</span>'
1805 . '</div>'
1806 . $toc
1807 . "</ul>\n</div>\n";
1808 }
1809
1820 public static function generateTOC( ?TOCData $tocData, Language $lang = null, array $options = [] ): string {
1821 $toc = '';
1822 $lastLevel = 0;
1823 $maxTocLevel = $options['maxtoclevel'] ?? null;
1824 foreach ( ( $tocData ? $tocData->getSections() : [] ) as $section ) {
1825 $tocLevel = $section->tocLevel;
1826 if ( $maxTocLevel !== null && $tocLevel < $maxTocLevel ) {
1827 if ( $tocLevel > $lastLevel ) {
1828 $toc .= self::tocIndent();
1829 } elseif ( $tocLevel < $lastLevel ) {
1830 if ( $lastLevel < $maxTocLevel ) {
1831 $toc .= self::tocUnindent(
1832 $lastLevel - $tocLevel );
1833 } else {
1834 $toc .= self::tocLineEnd();
1835 }
1836 } else {
1837 $toc .= self::tocLineEnd();
1838 }
1839
1840 $toc .= self::tocLine( $section->linkAnchor,
1841 $section->line, $section->number,
1842 $tocLevel, $section->index );
1843 $lastLevel = $tocLevel;
1844 }
1845 }
1846 if ( $lastLevel < $maxTocLevel && $lastLevel > 0 ) {
1847 $toc .= self::tocUnindent( $lastLevel - 1 );
1848 }
1849 return self::tocList( $toc, $lang );
1850 }
1851
1868 public static function makeHeadline( $level, $attribs, $anchor, $html,
1869 $link, $fallbackAnchor = false
1870 ) {
1871 $anchorEscaped = htmlspecialchars( $anchor, ENT_COMPAT );
1872 $fallback = '';
1873 if ( $fallbackAnchor !== false && $fallbackAnchor !== $anchor ) {
1874 $fallbackAnchor = htmlspecialchars( $fallbackAnchor, ENT_COMPAT );
1875 $fallback = "<span id=\"$fallbackAnchor\"></span>";
1876 }
1877 return "<h$level$attribs"
1878 . "$fallback<span class=\"mw-headline\" id=\"$anchorEscaped\">$html</span>"
1879 . $link
1880 . "</h$level>";
1881 }
1882
1889 public static function splitTrail( $trail ) {
1890 $regex = MediaWikiServices::getInstance()->getContentLanguage()->linkTrail();
1891 $inside = '';
1892 if ( $trail !== '' && preg_match( $regex, $trail, $m ) ) {
1893 [ , $inside, $trail ] = $m;
1894 }
1895 return [ $inside, $trail ];
1896 }
1897
1928 public static function generateRollback(
1929 RevisionRecord $revRecord,
1930 IContextSource $context = null,
1931 $options = []
1932 ) {
1933 $context ??= RequestContext::getMain();
1934
1935 $editCount = self::getRollbackEditCount( $revRecord );
1936 if ( $editCount === false ) {
1937 return '';
1938 }
1939
1940 $inner = self::buildRollbackLink( $revRecord, $context, $editCount );
1941
1942 // Allow extensions to modify the rollback link.
1943 // Abort further execution if the extension wants full control over the link.
1944 if ( !Hooks::runner()->onLinkerGenerateRollbackLink(
1945 $revRecord, $context, $options, $inner ) ) {
1946 return $inner;
1947 }
1948
1949 if ( !in_array( 'noBrackets', $options, true ) ) {
1950 $inner = $context->msg( 'brackets' )->rawParams( $inner )->escaped();
1951 }
1952
1953 if ( MediaWikiServices::getInstance()->getUserOptionsLookup()
1954 ->getBoolOption( $context->getUser(), 'showrollbackconfirmation' )
1955 ) {
1956 $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
1957 $stats->increment( 'rollbackconfirmation.event.load' );
1958 $context->getOutput()->addModules( 'mediawiki.misc-authed-curate' );
1959 }
1960
1961 return '<span class="mw-rollback-link">' . $inner . '</span>';
1962 }
1963
1982 public static function getRollbackEditCount( RevisionRecord $revRecord, $verify = true ) {
1983 if ( func_num_args() > 1 ) {
1984 wfDeprecated( __METHOD__ . ' with $verify parameter', '1.40' );
1985 }
1986 $showRollbackEditCount = MediaWikiServices::getInstance()->getMainConfig()
1987 ->get( MainConfigNames::ShowRollbackEditCount );
1988
1989 if ( !is_int( $showRollbackEditCount ) || !$showRollbackEditCount > 0 ) {
1990 // Nothing has happened, indicate this by returning 'null'
1991 return null;
1992 }
1993
1994 $dbr = wfGetDB( DB_REPLICA );
1995
1996 // Up to the value of $wgShowRollbackEditCount revisions are counted
1997 $revQuery = MediaWikiServices::getInstance()->getRevisionStore()->getQueryInfo();
1998 $res = $dbr->newSelectQueryBuilder()
1999 ->select( [ 'rev_user_text' => $revQuery['fields']['rev_user_text'], 'rev_deleted' ] )
2000 ->tables( $revQuery['tables'] )
2001 ->where( [ 'rev_page' => $revRecord->getPageId() ] )
2002 ->joinConds( $revQuery['joins'] )
2003 ->useIndex( [ 'revision' => 'rev_page_timestamp' ] )
2004 ->orderBy( [ 'rev_timestamp', 'rev_id' ], SelectQueryBuilder::SORT_DESC )
2005 ->limit( $showRollbackEditCount + 1 )
2006 ->caller( __METHOD__ )
2007 ->fetchResultSet();
2008
2009 $revUser = $revRecord->getUser( RevisionRecord::RAW );
2010 $revUserText = $revUser ? $revUser->getName() : '';
2011
2012 $editCount = 0;
2013 $moreRevs = false;
2014 foreach ( $res as $row ) {
2015 if ( $row->rev_user_text != $revUserText ) {
2016 if ( $row->rev_deleted & RevisionRecord::DELETED_TEXT
2017 || $row->rev_deleted & RevisionRecord::DELETED_USER
2018 ) {
2019 // If the user or the text of the revision we might rollback
2020 // to is deleted in some way we can't rollback. Similar to
2021 // the checks in WikiPage::commitRollback.
2022 return false;
2023 }
2024 $moreRevs = true;
2025 break;
2026 }
2027 $editCount++;
2028 }
2029
2030 if ( $editCount <= $showRollbackEditCount && !$moreRevs ) {
2031 // We didn't find at least $wgShowRollbackEditCount revisions made by the current user
2032 // and there weren't any other revisions. That means that the current user is the only
2033 // editor, so we can't rollback
2034 return false;
2035 }
2036 return $editCount;
2037 }
2038
2053 public static function buildRollbackLink(
2054 RevisionRecord $revRecord,
2055 IContextSource $context = null,
2056 $editCount = false
2057 ) {
2058 $config = MediaWikiServices::getInstance()->getMainConfig();
2059 $showRollbackEditCount = $config->get( MainConfigNames::ShowRollbackEditCount );
2060 $miserMode = $config->get( MainConfigNames::MiserMode );
2061 // To config which pages are affected by miser mode
2062 $disableRollbackEditCountSpecialPage = [ 'Recentchanges', 'Watchlist' ];
2063
2064 $context ??= RequestContext::getMain();
2065
2066 $title = $revRecord->getPageAsLinkTarget();
2067 $revUser = $revRecord->getUser();
2068 $revUserText = $revUser ? $revUser->getName() : '';
2069
2070 $query = [
2071 'action' => 'rollback',
2072 'from' => $revUserText,
2073 'token' => $context->getUser()->getEditToken( 'rollback' ),
2074 ];
2075
2076 $attrs = [
2077 'data-mw' => 'interface',
2078 'title' => $context->msg( 'tooltip-rollback' )->text()
2079 ];
2080
2081 $options = [ 'known', 'noclasses' ];
2082
2083 if ( $context->getRequest()->getBool( 'bot' ) ) {
2084 // T17999
2085 $query['hidediff'] = '1';
2086 $query['bot'] = '1';
2087 }
2088
2089 if ( $miserMode ) {
2090 foreach ( $disableRollbackEditCountSpecialPage as $specialPage ) {
2091 if ( $context->getTitle()->isSpecial( $specialPage ) ) {
2092 $showRollbackEditCount = false;
2093 break;
2094 }
2095 }
2096 }
2097
2098 // The edit count can be 0 on replica lag, fall back to the generic rollbacklink message
2099 $msg = [ 'rollbacklink' ];
2100 if ( is_int( $showRollbackEditCount ) && $showRollbackEditCount > 0 ) {
2101 if ( !is_numeric( $editCount ) ) {
2102 $editCount = self::getRollbackEditCount( $revRecord );
2103 }
2104
2105 if ( $editCount > $showRollbackEditCount ) {
2106 $msg = [ 'rollbacklinkcount-morethan', Message::numParam( $showRollbackEditCount ) ];
2107 } elseif ( $editCount ) {
2108 $msg = [ 'rollbacklinkcount', Message::numParam( $editCount ) ];
2109 }
2110 }
2111
2112 $html = $context->msg( ...$msg )->parse();
2113 return self::link( $title, $html, $attrs, $query, $options );
2114 }
2115
2124 public static function formatHiddenCategories( $hiddencats ) {
2125 $outText = '';
2126 if ( count( $hiddencats ) > 0 ) {
2127 # Construct the HTML
2128 $outText = '<div class="mw-hiddenCategoriesExplanation">';
2129 $outText .= wfMessage( 'hiddencategories' )->numParams( count( $hiddencats ) )->parseAsBlock();
2130 $outText .= "</div><ul>\n";
2131
2132 foreach ( $hiddencats as $titleObj ) {
2133 # If it's hidden, it must exist - no need to check with a LinkBatch
2134 $outText .= '<li>'
2135 . self::link( $titleObj, null, [], [], 'known' )
2136 . "</li>\n";
2137 }
2138 $outText .= '</ul>';
2139 }
2140 return $outText;
2141 }
2142
2146 private static function getContextFromMain() {
2147 $context = RequestContext::getMain();
2148 $context = new DerivativeContext( $context );
2149 return $context;
2150 }
2151
2169 public static function titleAttrib( $name, $options = null, array $msgParams = [], $localizer = null ) {
2170 if ( !$localizer ) {
2171 $localizer = self::getContextFromMain();
2172 }
2173 $message = $localizer->msg( "tooltip-$name", $msgParams );
2174 if ( $message->isDisabled() ) {
2175 $tooltip = false;
2176 } else {
2177 $tooltip = $message->text();
2178 # Compatibility: formerly some tooltips had [alt-.] hardcoded
2179 $tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip );
2180 }
2181
2182 $options = (array)$options;
2183
2184 if ( in_array( 'nonexisting', $options ) ) {
2185 $tooltip = $localizer->msg( 'red-link-title', $tooltip ?: '' )->text();
2186 }
2187 if ( in_array( 'withaccess', $options ) ) {
2188 $accesskey = self::accesskey( $name, $localizer );
2189 if ( $accesskey !== false ) {
2190 // Should be build the same as in jquery.accessKeyLabel.js
2191 if ( $tooltip === false || $tooltip === '' ) {
2192 $tooltip = $localizer->msg( 'brackets', $accesskey )->text();
2193 } else {
2194 $tooltip .= $localizer->msg( 'word-separator' )->text();
2195 $tooltip .= $localizer->msg( 'brackets', $accesskey )->text();
2196 }
2197 }
2198 }
2199
2200 return $tooltip;
2201 }
2202
2203 public static $accesskeycache;
2204
2217 public static function accesskey( $name, $localizer = null ) {
2218 if ( !isset( self::$accesskeycache[$name] ) ) {
2219 if ( !$localizer ) {
2220 $localizer = self::getContextFromMain();
2221 }
2222 $msg = $localizer->msg( "accesskey-$name" );
2223 self::$accesskeycache[$name] = $msg->isDisabled() ? false : $msg->plain();
2224 }
2225 return self::$accesskeycache[$name];
2226 }
2227
2242 public static function getRevDeleteLink(
2243 Authority $performer,
2244 RevisionRecord $revRecord,
2246 ) {
2247 $canHide = $performer->isAllowed( 'deleterevision' );
2248 $canHideHistory = $performer->isAllowed( 'deletedhistory' );
2249 if ( !$canHide && !( $revRecord->getVisibility() && $canHideHistory ) ) {
2250 return '';
2251 }
2252
2253 if ( !$revRecord->userCan( RevisionRecord::DELETED_RESTRICTED, $performer ) ) {
2254 return self::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
2255 }
2256 $prefixedDbKey = MediaWikiServices::getInstance()->getTitleFormatter()->
2257 getPrefixedDBkey( $title );
2258 if ( $revRecord->getId() ) {
2259 // RevDelete links using revision ID are stable across
2260 // page deletion and undeletion; use when possible.
2261 $query = [
2262 'type' => 'revision',
2263 'target' => $prefixedDbKey,
2264 'ids' => $revRecord->getId()
2265 ];
2266 } else {
2267 // Older deleted entries didn't save a revision ID.
2268 // We have to refer to these by timestamp, ick!
2269 $query = [
2270 'type' => 'archive',
2271 'target' => $prefixedDbKey,
2272 'ids' => $revRecord->getTimestamp()
2273 ];
2274 }
2275 return self::revDeleteLink(
2276 $query,
2277 $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ),
2278 $canHide
2279 );
2280 }
2281
2294 public static function revDeleteLink( $query = [], $restricted = false, $delete = true ) {
2295 $sp = SpecialPage::getTitleFor( 'Revisiondelete' );
2296 $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
2297 $html = wfMessage( $msgKey )->escaped();
2298 $tag = $restricted ? 'strong' : 'span';
2299 $link = self::link( $sp, $html, [], $query, [ 'known', 'noclasses' ] );
2300 return Xml::tags(
2301 $tag,
2302 [ 'class' => 'mw-revdelundel-link' ],
2303 wfMessage( 'parentheses' )->rawParams( $link )->escaped()
2304 );
2305 }
2306
2318 public static function revDeleteLinkDisabled( $delete = true ) {
2319 $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
2320 $html = wfMessage( $msgKey )->escaped();
2321 $htmlParentheses = wfMessage( 'parentheses' )->rawParams( $html )->escaped();
2322 return Xml::tags( 'span', [ 'class' => 'mw-revdelundel-link' ], $htmlParentheses );
2323 }
2324
2336 private static function updateWatchstarTooltipMessage(
2337 string &$tooltip, array &$msgParams, $config, $user, $relevantTitle
2338 ): void {
2339 if ( !$config || !$user || !$relevantTitle ) {
2340 $mainContext = self::getContextFromMain();
2341 if ( !$config ) {
2342 $config = $mainContext->getConfig();
2343 }
2344 if ( !$user ) {
2345 $user = $mainContext->getUser();
2346 }
2347 if ( !$relevantTitle ) {
2348 $relevantTitle = $mainContext->getSkin()->getRelevantTitle();
2349 }
2350 }
2351
2352 $isWatchlistExpiryEnabled = $config->get( MainConfigNames::WatchlistExpiry );
2353 if ( !$isWatchlistExpiryEnabled || !$relevantTitle || !$relevantTitle->canExist() ) {
2354 return;
2355 }
2356
2357 $watchStore = MediaWikiServices::getInstance()->getWatchedItemStore();
2358 $watchedItem = $watchStore->getWatchedItem( $user, $relevantTitle );
2359 if ( $watchedItem instanceof WatchedItem && $watchedItem->getExpiry() !== null ) {
2360 $diffInDays = $watchedItem->getExpiryInDays();
2361
2362 if ( $diffInDays ) {
2363 $msgParams = [ $diffInDays ];
2364 // Resolves to tooltip-ca-unwatch-expiring message
2365 $tooltip = 'ca-unwatch-expiring';
2366 } else { // Resolves to tooltip-ca-unwatch-expiring-hours message
2367 $tooltip = 'ca-unwatch-expiring-hours';
2368 }
2369 }
2370 }
2371
2388 public static function tooltipAndAccesskeyAttribs(
2389 $name,
2390 array $msgParams = [],
2391 $options = null,
2392 $localizer = null,
2393 $user = null,
2394 $config = null,
2395 $relevantTitle = null
2396 ) {
2397 $options = (array)$options;
2398 $options[] = 'withaccess';
2399 $tooltipTitle = $name;
2400
2401 // Get optional parameters from global context if any missing.
2402 if ( !$localizer ) {
2403 $localizer = self::getContextFromMain();
2404 }
2405
2406 // @since 1.35 - add a WatchlistExpiry feature flag to show new watchstar tooltip message
2407 if ( $name === 'ca-unwatch' ) {
2408 self::updateWatchstarTooltipMessage( $tooltipTitle, $msgParams, $config, $user, $relevantTitle );
2409 }
2410
2411 $attribs = [
2412 'title' => self::titleAttrib( $tooltipTitle, $options, $msgParams, $localizer ),
2413 'accesskey' => self::accesskey( $name, $localizer )
2414 ];
2415 if ( $attribs['title'] === false ) {
2416 unset( $attribs['title'] );
2417 }
2418 if ( $attribs['accesskey'] === false ) {
2419 unset( $attribs['accesskey'] );
2420 }
2421 return $attribs;
2422 }
2423
2431 public static function tooltip( $name, $options = null ) {
2432 $tooltip = self::titleAttrib( $name, $options );
2433 if ( $tooltip === false ) {
2434 return '';
2435 }
2436 return Xml::expandAttributes( [
2437 'title' => $tooltip
2438 ] );
2439 }
2440
2441}
2442
2443class_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:195
const NS_USER_TALK
Definition Defines.php:67
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
wfCgiToArray( $query)
This is the logical opposite of wfArrayToCgi(): it accepts a query string as its argument and returns...
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
$fallback
Definition MessagesAb.php:8
if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode $wgLang
Definition Setup.php:527
if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode $wgTitle
Definition Setup.php:527
if(!defined('MW_SETUP_CALLBACK'))
The persistent session ID (if any) loaded at startup.
Definition WebStart.php:88
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 ...
Class to parse and build external user names.
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition File.php:68
Hooks class.
Definition Hooks.php:38
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:30
Base class for language-specific code.
Definition Language.php:56
Basic media transform error class.
Base class for the output of MediaHandler::doTransform() and File::transform().
Static utilities for manipulating HTML strings.
This class is a collection of static functions that serve two purposes:
Definition Html.php:55
Some internal bits split of from Skin.php.
Definition Linker.php:67
static expandLocalLinks(string $html)
Helper function to expand local links.
Definition Linker.php:1492
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:1684
static revDeleteLink( $query=[], $restricted=false, $delete=true)
Creates a (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2294
static link( $target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition Linker.php:116
static tocLineEnd()
End a Table Of Contents line.
Definition Linker.php:1769
static blockLink( $userId, $userText)
Definition Linker.php:1344
static makeSelfLinkObj( $nt, $html='', $query='', $trail='', $prefix='', $hash='')
Make appropriate markup for a link to the current article.
Definition Linker.php:192
static getUploadUrl( $destFile, $query='')
Get the URL to upload a certain file.
Definition Linker.php:971
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:326
static makeMediaLinkObj( $title, $html='', $time=false)
Create a direct link to a given uploaded file.
Definition Linker.php:1002
static processResponsiveImages( $file, $thumb, $hp)
Process responsive images: add 1.5x and 2x subimages to the thumbnail, where applicable.
Definition Linker.php:861
static userTalkLink( $userId, $userText)
Definition Linker.php:1322
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:1662
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:1526
static normalizeSubpageLink( $contextTitle, $target, &$text)
Definition Linker.php:1565
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:1063
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:1227
static emailLink( $userId, $userText)
Definition Linker.php:1365
static formatHiddenCategories( $hiddencats)
Returns HTML for the "hidden categories on this page" list.
Definition Linker.php:2124
static getInvalidTitleDescription(IContextSource $context, $namespace, $title)
Get a message saying that an invalid title was encountered.
Definition Linker.php:226
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:1982
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition Linker.php:1110
static getImageLinkMTOParams( $frameParams, $query='', $parser=null)
Get the link parameters for MediaTransformOutput::toHtml() from given frame parameters supplied by th...
Definition Linker.php:575
static generateTOC(?TOCData $tocData, Language $lang=null, array $options=[])
Definition Linker.php:1820
static linkKnown( $target, $html=null, $customAttribs=[], $query=[], $options=[ 'known'])
Identical to link(), except $options defaults to 'known'.
Definition Linker.php:168
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:2242
static makeThumbLink2(LinkTarget $title, $file, $frameParams=[], $handlerParams=[], $time=false, $query='', array $classes=[], ?Parser $parser=null)
Definition Linker.php:651
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:1781
static makeHeadline( $level, $attribs, $anchor, $html, $link, $fallbackAnchor=false)
Create a headline for content.
Definition Linker.php:1868
static makeExternalImage( $url, $alt='')
Return the code for images which were added via external links, via Parser::maybeMakeExternalImage().
Definition Linker.php:269
static tooltip( $name, $options=null)
Returns raw bits of HTML, use titleAttrib()
Definition Linker.php:2431
static makeBrokenImageLinkObj( $title, $label='', $query='', $unused1='', $unused2='', $time=false, array $handlerParams=[], bool $currentExists=false)
Make a "broken" link to an image.
Definition Linker.php:898
static makeMediaLinkFile(LinkTarget $title, $file, $html='')
Create a direct link to a given uploaded file.
Definition Linker.php:1021
static accesskey( $name, $localizer=null)
Given the id of an interface element, constructs the appropriate accesskey attribute from the system ...
Definition Linker.php:2217
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:2169
static userToolLinksRedContribs( $userId, $userText, $edits=null, $useParentheses=true)
Alias for userToolLinks( $userId, $userText, true );.
Definition Linker.php:1310
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:1889
static tocIndent()
Add another level to the Table of Contents.
Definition Linker.php:1715
const TOOL_LINKS_NOBLOCK
Flags for userToolLinks()
Definition Linker.php:71
static revDeleteLinkDisabled( $delete=true)
Creates a dead (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2318
static tooltipAndAccesskeyAttribs( $name, array $msgParams=[], $options=null, $localizer=null, $user=null, $config=null, $relevantTitle=null)
Returns the attributes for the tooltip and access key.
Definition Linker.php:2388
static formatRevisionSize( $size)
Definition Linker.php:1700
static tocUnindent( $level)
Finish one or more sublevels on the Table of Contents.
Definition Linker.php:1726
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:1552
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:618
static userLink( $userId, $userName, $altUserName=false, $attributes=[])
Make user link (or user contributions for unregistered users)
Definition Linker.php:1164
static revUserLink(RevisionRecord $revRecord, $isPublic=false)
Generate a user link if the current user is allowed to view it.
Definition Linker.php:1391
static getRevisionDeletedClass(RevisionRecord $revisionRecord)
Returns css class of a deleted revision.
Definition Linker.php:1419
static generateRollback(RevisionRecord $revRecord, IContextSource $context=null, $options=[])
Generate a rollback link for a given revision.
Definition Linker.php:1928
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:1439
static tocLine( $linkAnchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition Linker.php:1741
static buildRollbackLink(RevisionRecord $revRecord, IContextSource $context=null, $editCount=false)
Build a raw rollback link, useful for collections of "tool" links.
Definition Linker.php:2053
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.
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.
castFromLinkTarget(?LinkTarget $linkTarget)
newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Represents a title within MediaWiki.
Definition Title.php:82
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:1146
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition Parser.php:107
getTargetLanguage()
Get the target language for the content being parsed.
Definition Parser.php:1163
getTitle()
Definition Parser.php:1014
getBadFileLookup()
Get the BadFileLookup instance that this Parser is using.
Definition Parser.php:1237
static getExternalLinkRel( $url=false, LinkTarget $title=null)
Get the rel attribute for a particular external link.
Definition Parser.php:2244
Group all the pieces relevant to the context of a request into one instance.
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,...
Represents a page (or page fragment) title within MediaWiki.
internal since 1.36
Definition User.php:71
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition User.php:626
Representation of a pair of user and title for watchlist entries.
getExpiry(?int $style=TS_MW)
When the watched item will expire.
A query builder for SELECT queries with a fluent interface.
Module of static functions for generating XML.
Definition Xml.php:31
Interface for configuration instances.
Definition Config.php:30
Interface for objects which can provide a MediaWiki context on request.
This interface represents the authority associated the current execution context, such as a web reque...
Definition Authority.php:37
isAllowed(string $permission)
Checks whether this authority has the given permission in general.
Interface for localizing messages in MediaWiki.
msg( $key,... $params)
This is the method for getting translated interface messages.
const DB_REPLICA
Definition defines.php:26
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42
if(!isset( $args[0])) $lang