MediaWiki REL1_38
Linker.php
Go to the documentation of this file.
1<?php
23use HtmlFormatter\HtmlFormatter;
28use Wikimedia\IPUtils;
29
39class Linker {
43 public const TOOL_LINKS_NOBLOCK = 1;
44 public const TOOL_LINKS_EMAIL = 2;
45
88 public static function link(
89 $target, $html = null, $customAttribs = [], $query = [], $options = []
90 ) {
91 if ( !$target instanceof LinkTarget ) {
92 wfWarn( __METHOD__ . ': Requires $target to be a LinkTarget object.', 2 );
93 return "<!-- ERROR -->$html";
94 }
95
96 $services = MediaWikiServices::getInstance();
97 $options = (array)$options;
98 if ( $options ) {
99 // Custom options, create new LinkRenderer
100 $linkRenderer = $services->getLinkRendererFactory()
101 ->createFromLegacyOptions( $options );
102 } else {
103 $linkRenderer = $services->getLinkRenderer();
104 }
105
106 if ( $html !== null ) {
107 $text = new HtmlArmor( $html );
108 } else {
109 $text = null;
110 }
111
112 if ( in_array( 'known', $options, true ) ) {
113 return $linkRenderer->makeKnownLink( $target, $text, $customAttribs, $query );
114 }
115
116 if ( in_array( 'broken', $options, true ) ) {
117 return $linkRenderer->makeBrokenLink( $target, $text, $customAttribs, $query );
118 }
119
120 if ( in_array( 'noclasses', $options, true ) ) {
121 return $linkRenderer->makePreloadedLink( $target, $text, '', $customAttribs, $query );
122 }
123
124 return $linkRenderer->makeLink( $target, $text, $customAttribs, $query );
125 }
126
140 public static function linkKnown(
141 $target, $html = null, $customAttribs = [],
142 $query = [], $options = [ 'known' ]
143 ) {
144 return self::link( $target, $html, $customAttribs, $query, $options );
145 }
146
162 public static function makeSelfLinkObj( $nt, $html = '', $query = '', $trail = '', $prefix = '' ) {
163 $nt = Title::newFromLinkTarget( $nt );
164 $ret = "<a class=\"mw-selflink selflink\">{$prefix}{$html}</a>{$trail}";
165 if ( !Hooks::runner()->onSelfLinkBegin( $nt, $html, $trail, $prefix, $ret ) ) {
166 return $ret;
167 }
168
169 if ( $html == '' ) {
170 $html = htmlspecialchars( $nt->getPrefixedText() );
171 }
172 list( $inside, $trail ) = self::splitTrail( $trail );
173 return "<a class=\"mw-selflink selflink\">{$prefix}{$html}{$inside}</a>{$trail}";
174 }
175
186 public static function getInvalidTitleDescription( IContextSource $context, $namespace, $title ) {
187 // First we check whether the namespace exists or not.
188 if ( MediaWikiServices::getInstance()->getNamespaceInfo()->exists( $namespace ) ) {
189 if ( $namespace == NS_MAIN ) {
190 $name = $context->msg( 'blanknamespace' )->text();
191 } else {
192 $name = MediaWikiServices::getInstance()->getContentLanguage()->
193 getFormattedNsText( $namespace );
194 }
195 return $context->msg( 'invalidtitle-knownnamespace', $namespace, $name, $title )->text();
196 }
197
198 return $context->msg( 'invalidtitle-unknownnamespace', $namespace, $title )->text();
199 }
200
207 public static function normaliseSpecialPage( LinkTarget $target ) {
208 wfDeprecated( __METHOD__, '1.35' );
209 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
210 return $linkRenderer->normalizeTarget( $target );
211 }
212
221 private static function fnamePart( $url ) {
222 $basename = strrchr( $url, '/' );
223 if ( $basename === false ) {
224 $basename = $url;
225 } else {
226 $basename = substr( $basename, 1 );
227 }
228 return $basename;
229 }
230
241 public static function makeExternalImage( $url, $alt = '' ) {
242 if ( $alt == '' ) {
243 $alt = self::fnamePart( $url );
244 }
245 $img = '';
246 $success = Hooks::runner()->onLinkerMakeExternalImage( $url, $alt, $img );
247 if ( !$success ) {
248 wfDebug( "Hook LinkerMakeExternalImage changed the output of external image "
249 . "with url {$url} and alt text {$alt} to {$img}" );
250 return $img;
251 }
252 return Html::element( 'img',
253 [
254 'src' => $url,
255 'alt' => $alt
256 ]
257 );
258 }
259
297 public static function makeImageLink( Parser $parser, LinkTarget $title,
298 $file, $frameParams = [], $handlerParams = [], $time = false,
299 $query = "", $widthOption = null
300 ) {
301 $title = Title::newFromLinkTarget( $title );
302 $res = null;
303 $dummy = new DummyLinker;
304 if ( !Hooks::runner()->onImageBeforeProduceHTML( $dummy, $title,
305 $file, $frameParams, $handlerParams, $time, $res,
306 $parser, $query, $widthOption )
307 ) {
308 return $res;
309 }
310
311 if ( $file && !$file->allowInlineDisplay() ) {
312 wfDebug( __METHOD__ . ': ' . $title->getPrefixedDBkey() . " does not allow inline display" );
313 return self::link( $title );
314 }
315
316 // Clean up parameters
317 $page = $handlerParams['page'] ?? false;
318 if ( !isset( $frameParams['align'] ) ) {
319 $frameParams['align'] = '';
320 }
321 if ( !isset( $frameParams['alt'] ) ) {
322 $frameParams['alt'] = '';
323 }
324 if ( !isset( $frameParams['title'] ) ) {
325 $frameParams['title'] = '';
326 }
327 if ( !isset( $frameParams['class'] ) ) {
328 $frameParams['class'] = '';
329 }
330
331 $services = MediaWikiServices::getInstance();
332 $config = $services->getMainConfig();
333 $enableLegacyMediaDOM = $config->get( 'ParserEnableLegacyMediaDOM' );
334
335 $classes = [];
336 if ( !isset( $handlerParams['width'] ) ) {
337 $classes[] = 'mw-default-size';
338 }
339
340 $prefix = $postfix = '';
341
342 if ( $enableLegacyMediaDOM ) {
343 if ( $frameParams['align'] == 'center' ) {
344 $prefix = '<div class="center">';
345 $postfix = '</div>';
346 $frameParams['align'] = 'none';
347 }
348 }
349
350 if ( $file && !isset( $handlerParams['width'] ) ) {
351 if ( isset( $handlerParams['height'] ) && $file->isVectorized() ) {
352 // If its a vector image, and user only specifies height
353 // we don't want it to be limited by its "normal" width.
354 $svgMaxSize = $config->get( 'SVGMaxSize' );
355 $handlerParams['width'] = $svgMaxSize;
356 } else {
357 $handlerParams['width'] = $file->getWidth( $page );
358 }
359
360 if ( isset( $frameParams['thumbnail'] )
361 || isset( $frameParams['manualthumb'] )
362 || isset( $frameParams['framed'] )
363 || isset( $frameParams['frameless'] )
364 || !$handlerParams['width']
365 ) {
366 $thumbLimits = $config->get( 'ThumbLimits' );
367 $thumbUpright = $config->get( 'ThumbUpright' );
368 if ( $widthOption === null || !isset( $thumbLimits[$widthOption] ) ) {
369 $userOptionsLookup = $services->getUserOptionsLookup();
370 $widthOption = $userOptionsLookup->getDefaultOption( 'thumbsize' );
371 }
372
373 // Reduce width for upright images when parameter 'upright' is used
374 if ( isset( $frameParams['upright'] ) && $frameParams['upright'] == 0 ) {
375 $frameParams['upright'] = $thumbUpright;
376 }
377
378 // For caching health: If width scaled down due to upright
379 // parameter, round to full __0 pixel to avoid the creation of a
380 // lot of odd thumbs.
381 $prefWidth = isset( $frameParams['upright'] ) ?
382 round( $thumbLimits[$widthOption] * $frameParams['upright'], -1 ) :
383 $thumbLimits[$widthOption];
384
385 // Use width which is smaller: real image width or user preference width
386 // Unless image is scalable vector.
387 if ( !isset( $handlerParams['height'] ) && ( $handlerParams['width'] <= 0 ||
388 $prefWidth < $handlerParams['width'] || $file->isVectorized() ) ) {
389 $handlerParams['width'] = $prefWidth;
390 }
391 }
392 }
393
394 if ( isset( $frameParams['thumbnail'] ) || isset( $frameParams['manualthumb'] )
395 || isset( $frameParams['framed'] )
396 ) {
397 if ( $enableLegacyMediaDOM ) {
398 // This is no longer needed in our new media output, since the
399 // default styling in content.media-common.less takes care of it;
400 // see T269704.
401
402 # Create a thumbnail. Alignment depends on the writing direction of
403 # the page content language (right-aligned for LTR languages,
404 # left-aligned for RTL languages)
405 # If a thumbnail width has not been provided, it is set
406 # to the default user option as specified in Language*.php
407 if ( $frameParams['align'] == '' ) {
408 $frameParams['align'] = $parser->getTargetLanguage()->alignEnd();
409 }
410 }
411 return $prefix . self::makeThumbLink2(
412 $title, $file, $frameParams, $handlerParams, $time, $query,
413 $classes, $parser
414 ) . $postfix;
415 }
416
417 switch ( $file ? $file->getMediaType() : '' ) {
418 case 'AUDIO':
419 $rdfaType = 'mw:Audio';
420 break;
421 case 'VIDEO':
422 $rdfaType = 'mw:Video';
423 break;
424 default:
425 $rdfaType = 'mw:Image';
426 }
427
428 if ( $file && isset( $frameParams['frameless'] ) ) {
429 $rdfaType .= '/Frameless';
430 $srcWidth = $file->getWidth( $page );
431 # For "frameless" option: do not present an image bigger than the
432 # source (for bitmap-style images). This is the same behavior as the
433 # "thumb" option does it already.
434 if ( $srcWidth && !$file->mustRender() && $handlerParams['width'] > $srcWidth ) {
435 $handlerParams['width'] = $srcWidth;
436 }
437 }
438
439 if ( $file && isset( $handlerParams['width'] ) ) {
440 # Create a resized image, without the additional thumbnail features
441 $thumb = $file->transform( $handlerParams );
442 } else {
443 $thumb = false;
444 }
445
446 $isBadFile = $file && $thumb &&
447 $parser->getBadFileLookup()->isBadFile( $title->getDBkey(), $parser->getTitle() );
448
449 if ( !$thumb || $isBadFile ) {
450 $rdfaType = 'mw:Error ' . $rdfaType;
451 $label = '';
452 if ( $enableLegacyMediaDOM ) {
453 // This is the information for tooltips for inline images which
454 // Parsoid stores in data-mw. See T273014
455 $label = $frameParams['title'];
456 }
458 $title, $label, '', '', '', (bool)$time, $handlerParams
459 );
460 } else {
461 self::processResponsiveImages( $file, $thumb, $handlerParams );
462 $params = [
463 'alt' => $frameParams['alt'],
464 'title' => $frameParams['title'],
465 ];
466 if ( $enableLegacyMediaDOM ) {
467 $params += [
468 'valign' => $frameParams['valign'] ?? false,
469 'img-class' => $frameParams['class'],
470 ];
471 if ( isset( $frameParams['border'] ) ) {
472 $params['img-class'] .= ( $params['img-class'] !== '' ? ' ' : '' ) . 'thumbborder';
473 }
474 }
475 $params = self::getImageLinkMTOParams( $frameParams, $query, $parser ) + $params;
476 $s = $thumb->toHtml( $params );
477 }
478
479 if ( $enableLegacyMediaDOM ) {
480 if ( $frameParams['align'] != '' ) {
481 $s = Html::rawElement(
482 'div',
483 [ 'class' => 'float' . $frameParams['align'] ],
484 $s
485 );
486 }
487 return str_replace( "\n", ' ', $prefix . $s . $postfix );
488 }
489
490 $wrapper = 'span';
491 $caption = '';
492
493 if ( $frameParams['align'] != '' ) {
494 $wrapper = 'figure';
495 // Possible values: mw-halign-left mw-halign-center mw-halign-right mw-halign-none
496 $classes[] = "mw-halign-{$frameParams['align']}";
497 $caption = Html::rawElement(
498 'figcaption', [], $frameParams['caption'] ?? ''
499 );
500 } elseif ( isset( $frameParams['valign'] ) ) {
501 // Possible values: mw-valign-middle mw-valign-baseline mw-valign-sub
502 // mw-valign-super mw-valign-top mw-valign-text-top mw-valign-bottom
503 // mw-valign-text-bottom
504 $classes[] = "mw-valign-{$frameParams['valign']}";
505 }
506
507 if ( isset( $frameParams['border'] ) ) {
508 $classes[] = 'mw-image-border';
509 }
510
511 if ( isset( $frameParams['class'] ) ) {
512 $classes[] = $frameParams['class'];
513 }
514
515 $attribs = [
516 'class' => $classes,
517 'typeof' => $rdfaType,
518 ];
519
520 $s = Html::rawElement( $wrapper, $attribs, $s . $caption );
521
522 return str_replace( "\n", ' ', $s );
523 }
524
533 private static function getImageLinkMTOParams( $frameParams, $query = '', $parser = null ) {
534 $mtoParams = [];
535 if ( isset( $frameParams['link-url'] ) && $frameParams['link-url'] !== '' ) {
536 $mtoParams['custom-url-link'] = $frameParams['link-url'];
537 if ( isset( $frameParams['link-target'] ) ) {
538 $mtoParams['custom-target-link'] = $frameParams['link-target'];
539 }
540 if ( $parser ) {
541 $extLinkAttrs = $parser->getExternalLinkAttribs( $frameParams['link-url'] );
542 foreach ( $extLinkAttrs as $name => $val ) {
543 // Currently could include 'rel' and 'target'
544 $mtoParams['parser-extlink-' . $name] = $val;
545 }
546 }
547 } elseif ( isset( $frameParams['link-title'] ) && $frameParams['link-title'] !== '' ) {
548 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
549 $mtoParams['custom-title-link'] = Title::newFromLinkTarget(
550 $linkRenderer->normalizeTarget( $frameParams['link-title'] )
551 );
552 if ( isset( $frameParams['link-title-query'] ) ) {
553 $mtoParams['custom-title-link-query'] = $frameParams['link-title-query'];
554 }
555 } elseif ( !empty( $frameParams['no-link'] ) ) {
556 // No link
557 } else {
558 $mtoParams['desc-link'] = true;
559 $mtoParams['desc-query'] = $query;
560 }
561 return $mtoParams;
562 }
563
576 public static function makeThumbLinkObj(
577 LinkTarget $title, $file, $label = '', $alt = '', $align = null,
578 $params = [], $framed = false, $manualthumb = ""
579 ) {
580 $frameParams = [
581 'alt' => $alt,
582 'caption' => $label,
583 'align' => $align
584 ];
585 if ( $framed ) {
586 $frameParams['framed'] = true;
587 }
588 if ( $manualthumb ) {
589 $frameParams['manualthumb'] = $manualthumb;
590 }
591 $classes = [ 'mw-default-size' ];
593 $title, $file, $frameParams, $params, false, '', $classes
594 );
595 }
596
608 public static function makeThumbLink2(
609 LinkTarget $title, $file, $frameParams = [], $handlerParams = [],
610 $time = false, $query = "", array $classes = [], ?Parser $parser = null
611 ) {
612 $exists = $file && $file->exists();
613
614 $services = MediaWikiServices::getInstance();
615 $enableLegacyMediaDOM = $services->getMainConfig()->get( 'ParserEnableLegacyMediaDOM' );
616
617 $page = $handlerParams['page'] ?? false;
618 if ( !isset( $frameParams['align'] ) ) {
619 $frameParams['align'] = '';
620 if ( $enableLegacyMediaDOM ) {
621 $frameParams['align'] = 'right';
622 }
623 }
624 if ( !isset( $frameParams['alt'] ) ) {
625 $frameParams['alt'] = '';
626 }
627 if ( !isset( $frameParams['title'] ) ) {
628 $frameParams['title'] = '';
629 }
630 if ( !isset( $frameParams['caption'] ) ) {
631 $frameParams['caption'] = '';
632 }
633
634 if ( empty( $handlerParams['width'] ) ) {
635 // Reduce width for upright images when parameter 'upright' is used
636 $handlerParams['width'] = isset( $frameParams['upright'] ) ? 130 : 180;
637 }
638
639 $thumb = false;
640 $noscale = false;
641 $manualthumb = false;
642 $rdfaType = null;
643
644 if ( !$exists ) {
645 $outerWidth = $handlerParams['width'] + 2;
646 } else {
647 if ( isset( $frameParams['manualthumb'] ) ) {
648 # Use manually specified thumbnail
649 $manual_title = Title::makeTitleSafe( NS_FILE, $frameParams['manualthumb'] );
650 if ( $manual_title ) {
651 $manual_img = $services->getRepoGroup()
652 ->findFile( $manual_title );
653 if ( $manual_img ) {
654 $thumb = $manual_img->getUnscaledThumb( $handlerParams );
655 $manualthumb = true;
656 } else {
657 $exists = false;
658 }
659 }
660 } elseif ( isset( $frameParams['framed'] ) ) {
661 // Use image dimensions, don't scale
662 $thumb = $file->getUnscaledThumb( $handlerParams );
663 $noscale = true;
664 $rdfaType = '/Frame';
665 } else {
666 # Do not present an image bigger than the source, for bitmap-style images
667 # This is a hack to maintain compatibility with arbitrary pre-1.10 behavior
668 $srcWidth = $file->getWidth( $page );
669 if ( $srcWidth && !$file->mustRender() && $handlerParams['width'] > $srcWidth ) {
670 $handlerParams['width'] = $srcWidth;
671 }
672 $thumb = $file->transform( $handlerParams );
673 }
674
675 if ( $thumb ) {
676 $outerWidth = $thumb->getWidth() + 2;
677 } else {
678 $outerWidth = $handlerParams['width'] + 2;
679 }
680 }
681
682 $url = Title::newFromLinkTarget( $title )->getLocalURL( $query );
683 $linkTitleQuery = [];
684
685 if ( $page ) {
686 $linkTitleQuery['page'] = $page;
687 # ThumbnailImage::toHtml() already adds page= onto the end of DjVu URLs
688 # So we don't need to pass it here in $query. However, the URL for the
689 # zoom icon still needs it, so we make a unique query for it. See T16771
690 # FIXME: What about "lang" and other querystring parameters
691 $url = wfAppendQuery( $url, $linkTitleQuery );
692 }
693
694 if ( $manualthumb
695 && !isset( $frameParams['link-title'] )
696 && !isset( $frameParams['link-url'] )
697 && !isset( $frameParams['no-link'] ) ) {
698 $frameParams['link-title'] = $title;
699 $frameParams['link-title-query'] = $linkTitleQuery;
700 }
701
702 if ( $frameParams['align'] != '' ) {
703 // Possible values: mw-halign-left mw-halign-center mw-halign-right mw-halign-none
704 $classes[] = "mw-halign-{$frameParams['align']}";
705 }
706
707 if ( isset( $frameParams['class'] ) ) {
708 $classes[] = $frameParams['class'];
709 }
710
711 $s = '';
712
713 if ( $enableLegacyMediaDOM ) {
714 $s .= "<div class=\"thumb t{$frameParams['align']}\">"
715 . "<div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
716 }
717
718 $isBadFile = $exists && $thumb && $parser &&
719 $parser->getBadFileLookup()->isBadFile(
720 $manualthumb ? $manual_title : $title->getDBkey(),
721 $parser->getTitle()
722 );
723
724 if ( !$exists ) {
725 $label = '';
726 if ( $enableLegacyMediaDOM ) {
727 // This is the information for tooltips for inline images which
728 // Parsoid stores in data-mw. See T273014
729 $label = $frameParams['title'];
730 }
732 $title, $label, '', '', '', (bool)$time, $handlerParams
733 );
734 $zoomIcon = '';
735 } elseif ( !$thumb || $isBadFile ) {
736 if ( $enableLegacyMediaDOM ) {
737 $s .= wfMessage( 'thumbnail_error', '' )->escaped();
738 } else {
740 $title, '', '', '', '', (bool)$time, $handlerParams
741 );
742 }
743 $zoomIcon = '';
744 } else {
745 if ( !$noscale && !$manualthumb ) {
746 self::processResponsiveImages( $file, $thumb, $handlerParams );
747 }
748 $params = [
749 'alt' => $frameParams['alt'],
750 'title' => $frameParams['title'],
751 ];
752 if ( $enableLegacyMediaDOM ) {
753 $params += [
754 'img-class' => ( isset( $frameParams['class'] ) && $frameParams['class'] !== ''
755 ? $frameParams['class'] . ' '
756 : '' ) . 'thumbimage'
757 ];
758 }
759 $params = self::getImageLinkMTOParams( $frameParams, $query, $parser ) + $params;
760 $s .= $thumb->toHtml( $params );
761 if ( isset( $frameParams['framed'] ) ) {
762 $zoomIcon = "";
763 } else {
764 $zoomIcon = Html::rawElement( 'div', [ 'class' => 'magnify' ],
765 Html::rawElement( 'a', [
766 'href' => $url,
767 'class' => 'internal',
768 'title' => wfMessage( 'thumbnail-more' )->text() ],
769 "" ) );
770 }
771 }
772
773 if ( $enableLegacyMediaDOM ) {
774 $s .= ' <div class="thumbcaption">' . $zoomIcon . $frameParams['caption'] . "</div></div></div>";
775 return str_replace( "\n", ' ', $s );
776 }
777
778 $s .= Html::rawElement(
779 'figcaption', [], $frameParams['caption'] ?? ''
780 );
781
782 $rdfaType = $rdfaType ?: '/Thumb';
783
784 switch ( $file ? $file->getMediaType() : '' ) {
785 case 'AUDIO':
786 $rdfaType = 'mw:Audio' . $rdfaType;
787 break;
788 case 'VIDEO':
789 $rdfaType = 'mw:Video' . $rdfaType;
790 break;
791 default:
792 $rdfaType = 'mw:Image' . $rdfaType;
793 }
794
795 if ( !$exists || !$thumb ) {
796 $rdfaType = 'mw:Error ' . $rdfaType;
797 }
798
799 $attribs = [
800 'class' => $classes,
801 'typeof' => $rdfaType,
802 ];
803
804 $s = Html::rawElement( 'figure', $attribs, $s );
805
806 return str_replace( "\n", ' ', $s );
807 }
808
817 public static function processResponsiveImages( $file, $thumb, $hp ) {
818 $responsiveImages = MediaWikiServices::getInstance()->getMainConfig()->get( 'ResponsiveImages' );
819 if ( $responsiveImages && $thumb && !$thumb->isError() ) {
820 $hp15 = $hp;
821 $hp15['width'] = round( $hp['width'] * 1.5 );
822 $hp20 = $hp;
823 $hp20['width'] = $hp['width'] * 2;
824 if ( isset( $hp['height'] ) ) {
825 $hp15['height'] = round( $hp['height'] * 1.5 );
826 $hp20['height'] = $hp['height'] * 2;
827 }
828
829 $thumb15 = $file->transform( $hp15 );
830 $thumb20 = $file->transform( $hp20 );
831 if ( $thumb15 && !$thumb15->isError() && $thumb15->getUrl() !== $thumb->getUrl() ) {
832 $thumb->responsiveUrls['1.5'] = $thumb15->getUrl();
833 }
834 if ( $thumb20 && !$thumb20->isError() && $thumb20->getUrl() !== $thumb->getUrl() ) {
835 $thumb->responsiveUrls['2'] = $thumb20->getUrl();
836 }
837 }
838 }
839
853 public static function makeBrokenImageLinkObj(
854 $title, $label = '', $query = '', $unused1 = '', $unused2 = '',
855 $time = false, array $handlerParams = []
856 ) {
857 if ( !$title instanceof LinkTarget ) {
858 wfWarn( __METHOD__ . ': Requires $title to be a LinkTarget object.' );
859 return "<!-- ERROR -->" . htmlspecialchars( $label );
860 }
861
862 $title = Title::castFromLinkTarget( $title );
863 $services = MediaWikiServices::getInstance();
864 $mainConfig = $services->getMainConfig();
865 $enableUploads = $mainConfig->get( 'EnableUploads' );
866 $uploadMissingFileUrl = $mainConfig->get( 'UploadMissingFileUrl' );
867 $uploadNavigationUrl = $mainConfig->get( 'UploadNavigationUrl' );
868 if ( $label == '' ) {
869 $label = $title->getPrefixedText();
870 }
871
872 $html = Html::element( 'span', [
873 // These data attributes are used to dynamically size the span, see T273013
874 'data-width' => $handlerParams['width'] ?? null,
875 'data-height' => $handlerParams['height'] ?? null,
876 ], $label );
877
878 if ( $mainConfig->get( 'ParserEnableLegacyMediaDOM' ) ) {
879 $html = htmlspecialchars( $label, ENT_COMPAT );
880 }
881
882 $repoGroup = $services->getRepoGroup();
883 $currentExists = $time
884 && $repoGroup->findFile( $title ) !== false;
885
886 if ( ( $uploadMissingFileUrl || $uploadNavigationUrl || $enableUploads )
887 && !$currentExists
888 ) {
889 if ( $repoGroup->getLocalRepo()->checkRedirect( $title ) ) {
890 // We already know it's a redirect, so mark it accordingly
891 return self::link(
892 $title,
893 $html,
894 [ 'class' => 'mw-redirect' ],
895 wfCgiToArray( $query ),
896 [ 'known', 'noclasses' ]
897 );
898 }
899 return Html::rawElement( 'a', [
900 'href' => self::getUploadUrl( $title, $query ),
901 'class' => 'new',
902 'title' => $title->getPrefixedText()
903 ], $html );
904 }
905 return self::link(
906 $title,
907 $html,
908 [],
909 wfCgiToArray( $query ),
910 [ 'known', 'noclasses' ]
911 );
912 }
913
922 protected static function getUploadUrl( $destFile, $query = '' ) {
923 $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
924 $uploadMissingFileUrl = $mainConfig->get( 'UploadMissingFileUrl' );
925 $uploadNavigationUrl = $mainConfig->get( 'UploadNavigationUrl' );
926 $q = 'wpDestFile=' . Title::castFromLinkTarget( $destFile )->getPartialURL();
927 if ( $query != '' ) {
928 $q .= '&' . $query;
929 }
930
931 if ( $uploadMissingFileUrl ) {
932 return wfAppendQuery( $uploadMissingFileUrl, $q );
933 }
934
935 if ( $uploadNavigationUrl ) {
936 return wfAppendQuery( $uploadNavigationUrl, $q );
937 }
938
939 $upload = SpecialPage::getTitleFor( 'Upload' );
940
941 return $upload->getLocalURL( $q );
942 }
943
953 public static function makeMediaLinkObj( $title, $html = '', $time = false ) {
954 $img = MediaWikiServices::getInstance()->getRepoGroup()->findFile(
955 $title, [ 'time' => $time ]
956 );
957 return self::makeMediaLinkFile( $title, $img, $html );
958 }
959
972 public static function makeMediaLinkFile( LinkTarget $title, $file, $html = '' ) {
973 if ( $file && $file->exists() ) {
974 $url = $file->getUrl();
975 $class = 'internal';
976 } else {
977 $url = self::getUploadUrl( $title );
978 $class = 'new';
979 }
980
981 $alt = $title->getText();
982 if ( $html == '' ) {
983 $html = $alt;
984 }
985
986 $ret = '';
987 $attribs = [
988 'href' => $url,
989 'class' => $class,
990 'title' => $alt
991 ];
992
993 if ( !Hooks::runner()->onLinkerMakeMediaLinkFile(
994 Title::castFromLinkTarget( $title ), $file, $html, $attribs, $ret )
995 ) {
996 wfDebug( "Hook LinkerMakeMediaLinkFile changed the output of link "
997 . "with url {$url} and text {$html} to {$ret}" );
998 return $ret;
999 }
1000
1001 return Html::rawElement( 'a', $attribs, $html );
1002 }
1003
1014 public static function specialLink( $name, $key = '' ) {
1015 if ( $key == '' ) {
1016 $key = strtolower( $name );
1017 }
1018
1019 return self::linkKnown( SpecialPage::getTitleFor( $name ), wfMessage( $key )->escaped() );
1020 }
1021
1040 public static function makeExternalLink( $url, $text, $escape = true,
1041 $linktype = '', $attribs = [], $title = null
1042 ) {
1043 global $wgTitle;
1044 $class = "external";
1045 if ( $linktype ) {
1046 $class .= " $linktype";
1047 }
1048 if ( isset( $attribs['class'] ) && $attribs['class'] ) {
1049 $class .= " {$attribs['class']}";
1050 }
1051 $attribs['class'] = $class;
1052
1053 if ( $escape ) {
1054 $text = htmlspecialchars( $text, ENT_COMPAT );
1055 }
1056
1057 if ( !$title ) {
1058 $title = $wgTitle;
1059 }
1060 $newRel = Parser::getExternalLinkRel( $url, $title );
1061 if ( !isset( $attribs['rel'] ) || $attribs['rel'] === '' ) {
1062 $attribs['rel'] = $newRel;
1063 } elseif ( $newRel !== null ) {
1064 // Merge the rel attributes.
1065 $newRels = explode( ' ', $newRel );
1066 $oldRels = explode( ' ', $attribs['rel'] );
1067 $combined = array_unique( array_merge( $newRels, $oldRels ) );
1068 $attribs['rel'] = implode( ' ', $combined );
1069 }
1070 $link = '';
1071 $success = Hooks::runner()->onLinkerMakeExternalLink(
1072 $url, $text, $link, $attribs, $linktype );
1073 if ( !$success ) {
1074 wfDebug( "Hook LinkerMakeExternalLink changed the output of link "
1075 . "with url {$url} and text {$text} to {$link}" );
1076 return $link;
1077 }
1078 $attribs['href'] = $url;
1079 return Html::rawElement( 'a', $attribs, $text );
1080 }
1081
1093 public static function userLink( $userId, $userName, $altUserName = false ) {
1094 if ( $userName === '' || $userName === false || $userName === null ) {
1095 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1096 'that need to be fixed?' );
1097 return wfMessage( 'empty-username' )->parse();
1098 }
1099
1100 $classes = 'mw-userlink';
1101 $page = null;
1102 if ( $userId == 0 ) {
1103 $page = ExternalUserNames::getUserLinkTitle( $userName );
1104
1105 if ( ExternalUserNames::isExternal( $userName ) ) {
1106 $classes .= ' mw-extuserlink';
1107 } elseif ( $altUserName === false ) {
1108 $altUserName = IPUtils::prettifyIP( $userName );
1109 }
1110 $classes .= ' mw-anonuserlink'; // Separate link class for anons (T45179)
1111 } else {
1112 $page = TitleValue::tryNew( NS_USER, strtr( $userName, ' ', '_' ) );
1113 }
1114
1115 // Wrap the output with <bdi> tags for directionality isolation
1116 $linkText =
1117 '<bdi>' . htmlspecialchars( $altUserName !== false ? $altUserName : $userName ) . '</bdi>';
1118
1119 return $page
1120 ? self::link( $page, $linkText, [ 'class' => $classes ] )
1121 : Html::rawElement( 'span', [ 'class' => $classes ], $linkText );
1122 }
1123
1138 public static function userToolLinks(
1139 $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits = null,
1140 $useParentheses = true
1141 ) {
1142 if ( $userText === '' ) {
1143 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1144 'that need to be fixed?' );
1145 return ' ' . wfMessage( 'empty-username' )->parse();
1146 }
1147 global $wgLang;
1148 $disableAnonTalk = MediaWikiServices::getInstance()->getMainConfig()->get( 'DisableAnonTalk' );
1149 $talkable = !( $disableAnonTalk && $userId == 0 );
1150 $blockable = !( $flags & self::TOOL_LINKS_NOBLOCK );
1151 $addEmailLink = $flags & self::TOOL_LINKS_EMAIL && $userId;
1152
1153 if ( $userId == 0 && ExternalUserNames::isExternal( $userText ) ) {
1154 // No tools for an external user
1155 return '';
1156 }
1157
1158 $items = [];
1159 if ( $talkable ) {
1160 $items[] = self::userTalkLink( $userId, $userText );
1161 }
1162 if ( $userId ) {
1163 // check if the user has an edit
1164 $attribs = [];
1165 $attribs['class'] = 'mw-usertoollinks-contribs';
1166 if ( $redContribsWhenNoEdits ) {
1167 if ( intval( $edits ) === 0 && $edits !== 0 ) {
1168 $user = User::newFromId( $userId );
1169 $edits = $user->getEditCount();
1170 }
1171 if ( $edits === 0 ) {
1172 $attribs['class'] .= ' new';
1173 }
1174 }
1175 $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText );
1176
1177 $items[] = self::link( $contribsPage, wfMessage( 'contribslink' )->escaped(), $attribs );
1178 }
1179 $userCanBlock = RequestContext::getMain()->getAuthority()->isAllowed( 'block' );
1180 if ( $blockable && $userCanBlock ) {
1181 $items[] = self::blockLink( $userId, $userText );
1182 }
1183
1184 $user = RequestContext::getMain()->getUser();
1185 if ( $addEmailLink && $user->canSendEmail() ) {
1186 $items[] = self::emailLink( $userId, $userText );
1187 }
1188
1189 Hooks::runner()->onUserToolLinksEdit( $userId, $userText, $items );
1190
1191 if ( !$items ) {
1192 return '';
1193 }
1194
1195 if ( $useParentheses ) {
1196 return wfMessage( 'word-separator' )->escaped()
1197 . '<span class="mw-usertoollinks">'
1198 . wfMessage( 'parentheses' )->rawParams( $wgLang->pipeList( $items ) )->escaped()
1199 . '</span>';
1200 }
1201
1202 $tools = [];
1203 foreach ( $items as $tool ) {
1204 $tools[] = Html::rawElement( 'span', [], $tool );
1205 }
1206 return ' <span class="mw-usertoollinks mw-changeslist-links">' .
1207 implode( ' ', $tools ) . '</span>';
1208 }
1209
1219 public static function userToolLinksRedContribs(
1220 $userId, $userText, $edits = null, $useParentheses = true
1221 ) {
1222 return self::userToolLinks( $userId, $userText, true, 0, $edits, $useParentheses );
1223 }
1224
1231 public static function userTalkLink( $userId, $userText ) {
1232 if ( $userText === '' ) {
1233 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1234 'that need to be fixed?' );
1235 return wfMessage( 'empty-username' )->parse();
1236 }
1237
1238 $userTalkPage = TitleValue::tryNew( NS_USER_TALK, strtr( $userText, ' ', '_' ) );
1239 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-talk' ];
1240 $linkText = wfMessage( 'talkpagelinktext' )->escaped();
1241
1242 return $userTalkPage
1243 ? self::link( $userTalkPage, $linkText, $moreLinkAttribs )
1244 : Html::rawElement( 'span', $moreLinkAttribs, $linkText );
1245 }
1246
1253 public static function blockLink( $userId, $userText ) {
1254 if ( $userText === '' ) {
1255 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1256 'that need to be fixed?' );
1257 return wfMessage( 'empty-username' )->parse();
1258 }
1259
1260 $blockPage = SpecialPage::getTitleFor( 'Block', $userText );
1261 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-block' ];
1262
1263 return self::link( $blockPage,
1264 wfMessage( 'blocklink' )->escaped(),
1265 $moreLinkAttribs
1266 );
1267 }
1268
1274 public static function emailLink( $userId, $userText ) {
1275 if ( $userText === '' ) {
1276 wfLogWarning( __METHOD__ . ' received an empty username. Are there database errors ' .
1277 'that need to be fixed?' );
1278 return wfMessage( 'empty-username' )->parse();
1279 }
1280
1281 $emailPage = SpecialPage::getTitleFor( 'Emailuser', $userText );
1282 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-mail' ];
1283 return self::link( $emailPage,
1284 wfMessage( 'emaillink' )->escaped(),
1285 $moreLinkAttribs
1286 );
1287 }
1288
1300 public static function revUserLink( RevisionRecord $revRecord, $isPublic = false ) {
1301 // TODO inject authority
1302 $authority = RequestContext::getMain()->getAuthority();
1303
1304 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) && $isPublic ) {
1305 $link = wfMessage( 'rev-deleted-user' )->escaped();
1306 } elseif ( $revRecord->userCan( RevisionRecord::DELETED_USER, $authority ) ) {
1307 $revUser = $revRecord->getUser( RevisionRecord::FOR_THIS_USER, $authority );
1308 $link = self::userLink(
1309 $revUser ? $revUser->getId() : 0,
1310 $revUser ? $revUser->getName() : ''
1311 );
1312 } else {
1313 $link = wfMessage( 'rev-deleted-user' )->escaped();
1314 }
1315 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
1316 $class = self::getRevisionDeletedClass( $revRecord );
1317 return '<span class="' . $class . '">' . $link . '</span>';
1318 }
1319 return $link;
1320 }
1321
1328 public static function getRevisionDeletedClass( RevisionRecord $revisionRecord ): string {
1329 $class = 'history-deleted';
1330 if ( $revisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
1331 $class .= ' mw-history-suppressed';
1332 }
1333 return $class;
1334 }
1335
1348 public static function revUserTools(
1349 RevisionRecord $revRecord,
1350 $isPublic = false,
1351 $useParentheses = true
1352 ) {
1353 // TODO inject authority
1354 $authority = RequestContext::getMain()->getAuthority();
1355
1356 if ( $revRecord->userCan( RevisionRecord::DELETED_USER, $authority ) &&
1357 ( !$revRecord->isDeleted( RevisionRecord::DELETED_USER ) || !$isPublic )
1358 ) {
1359 $revUser = $revRecord->getUser( RevisionRecord::FOR_THIS_USER, $authority );
1360 $userId = $revUser ? $revUser->getId() : 0;
1361 $userText = $revUser ? $revUser->getName() : '';
1362
1363 if ( $userId || $userText !== '' ) {
1364 $link = self::userLink( $userId, $userText )
1365 . self::userToolLinks( $userId, $userText, false, 0, null,
1366 $useParentheses );
1367 }
1368 }
1369
1370 if ( !isset( $link ) ) {
1371 $link = wfMessage( 'rev-deleted-user' )->escaped();
1372 }
1373
1374 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
1375 $class = self::getRevisionDeletedClass( $revRecord );
1376 return ' <span class="' . $class . ' mw-userlink">' . $link . '</span>';
1377 }
1378 return $link;
1379 }
1380
1391 public static function expandLocalLinks( string $html ) {
1392 $formatter = new HtmlFormatter( $html );
1393 $doc = $formatter->getDoc();
1394 $xpath = new DOMXPath( $doc );
1395 $nodes = $xpath->query( '//a[@href]' );
1397 foreach ( $nodes as $node ) {
1398 $node->setAttribute(
1399 'href',
1400 wfExpandUrl( $node->getAttribute( 'href' ), PROTO_RELATIVE )
1401 );
1402 }
1403 return $formatter->getText( 'html' );
1404 }
1405
1426 public static function formatComment(
1427 $comment, $title = null, $local = false, $wikiId = null
1428 ) {
1429 $formatter = MediaWikiServices::getInstance()->getCommentFormatter();
1430 return $formatter->format( $comment, $title, $local, $wikiId );
1431 }
1432
1452 public static function formatLinksInComment(
1453 $comment, $title = null, $local = false, $wikiId = null
1454 ) {
1455 $formatter = MediaWikiServices::getInstance()->getCommentFormatter();
1456 return $formatter->formatLinksUnsafe( $comment, $title, $local, $wikiId );
1457 }
1458
1465 public static function normalizeSubpageLink( $contextTitle, $target, &$text ) {
1466 # Valid link forms:
1467 # Foobar -- normal
1468 # :Foobar -- override special treatment of prefix (images, language links)
1469 # /Foobar -- convert to CurrentPage/Foobar
1470 # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial and final / from text
1471 # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage
1472 # ../Foobar -- convert to CurrentPage/Foobar,
1473 # (from CurrentPage/CurrentSubPage)
1474 # ../Foobar/ -- convert to CurrentPage/Foobar, use 'Foobar' as text
1475 # (from CurrentPage/CurrentSubPage)
1476
1477 $ret = $target; # default return value is no change
1478
1479 # Some namespaces don't allow subpages,
1480 # so only perform processing if subpages are allowed
1481 if (
1482 $contextTitle && MediaWikiServices::getInstance()->getNamespaceInfo()->
1483 hasSubpages( $contextTitle->getNamespace() )
1484 ) {
1485 $hash = strpos( $target, '#' );
1486 if ( $hash !== false ) {
1487 $suffix = substr( $target, $hash );
1488 $target = substr( $target, 0, $hash );
1489 } else {
1490 $suffix = '';
1491 }
1492 # T9425
1493 $target = trim( $target );
1494 $contextPrefixedText = MediaWikiServices::getInstance()->getTitleFormatter()->
1495 getPrefixedText( $contextTitle );
1496 # Look at the first character
1497 if ( $target != '' && $target[0] === '/' ) {
1498 # / at end means we don't want the slash to be shown
1499 $m = [];
1500 $trailingSlashes = preg_match_all( '%(/+)$%', $target, $m );
1501 if ( $trailingSlashes ) {
1502 $noslash = $target = substr( $target, 1, -strlen( $m[0][0] ) );
1503 } else {
1504 $noslash = substr( $target, 1 );
1505 }
1506
1507 $ret = $contextPrefixedText . '/' . trim( $noslash ) . $suffix;
1508 if ( $text === '' ) {
1509 $text = $target . $suffix;
1510 } # this might be changed for ugliness reasons
1511 } else {
1512 # check for .. subpage backlinks
1513 $dotdotcount = 0;
1514 $nodotdot = $target;
1515 while ( strncmp( $nodotdot, "../", 3 ) == 0 ) {
1516 ++$dotdotcount;
1517 $nodotdot = substr( $nodotdot, 3 );
1518 }
1519 if ( $dotdotcount > 0 ) {
1520 $exploded = explode( '/', $contextPrefixedText );
1521 if ( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page
1522 $ret = implode( '/', array_slice( $exploded, 0, -$dotdotcount ) );
1523 # / at the end means don't show full path
1524 if ( substr( $nodotdot, -1, 1 ) === '/' ) {
1525 $nodotdot = rtrim( $nodotdot, '/' );
1526 if ( $text === '' ) {
1527 $text = $nodotdot . $suffix;
1528 }
1529 }
1530 $nodotdot = trim( $nodotdot );
1531 if ( $nodotdot != '' ) {
1532 $ret .= '/' . $nodotdot;
1533 }
1534 $ret .= $suffix;
1535 }
1536 }
1537 }
1538 }
1539
1540 return $ret;
1541 }
1542
1562 public static function commentBlock(
1563 $comment, $title = null, $local = false, $wikiId = null, $useParentheses = true
1564 ) {
1565 return MediaWikiServices::getInstance()->getCommentFormatter()
1566 ->formatBlock( $comment, $title, $local, $wikiId, $useParentheses );
1567 }
1568
1584 public static function revComment(
1585 RevisionRecord $revRecord,
1586 $local = false,
1587 $isPublic = false,
1588 $useParentheses = true
1589 ) {
1590 $authority = RequestContext::getMain()->getAuthority();
1591 $formatter = MediaWikiServices::getInstance()->getCommentFormatter();
1592 return $formatter->formatRevision( $revRecord, $authority, $local, $isPublic, $useParentheses );
1593 }
1594
1600 public static function formatRevisionSize( $size ) {
1601 if ( $size == 0 ) {
1602 $stxt = wfMessage( 'historyempty' )->escaped();
1603 } else {
1604 $stxt = wfMessage( 'nbytes' )->numParams( $size )->escaped();
1605 }
1606 return "<span class=\"history-size mw-diff-bytes\" data-mw-bytes=\"$size\">$stxt</span>";
1607 }
1608
1615 public static function tocIndent() {
1616 return "\n<ul>\n";
1617 }
1618
1626 public static function tocUnindent( $level ) {
1627 return "</li>\n" . str_repeat( "</ul>\n</li>\n", $level > 0 ? $level : 0 );
1628 }
1629
1641 public static function tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex = false ) {
1642 $classes = "toclevel-$level";
1643 if ( $sectionIndex !== false ) {
1644 $classes .= " tocsection-$sectionIndex";
1645 }
1646
1647 // <li class="$classes"><a href="#$anchor"><span class="tocnumber">
1648 // $tocnumber</span> <span class="toctext">$tocline</span></a>
1649 return Html::openElement( 'li', [ 'class' => $classes ] )
1650 . Html::rawElement( 'a',
1651 [ 'href' => "#$anchor" ],
1652 Html::element( 'span', [ 'class' => 'tocnumber' ], $tocnumber )
1653 . ' '
1654 . Html::rawElement( 'span', [ 'class' => 'toctext' ], $tocline )
1655 );
1656 }
1657
1665 public static function tocLineEnd() {
1666 return "</li>\n";
1667 }
1668
1677 public static function tocList( $toc, Language $lang = null ) {
1678 $lang = $lang ?? RequestContext::getMain()->getLanguage();
1679
1680 $title = wfMessage( 'toc' )->inLanguage( $lang )->escaped();
1681
1682 return '<div id="toc" class="toc" role="navigation" aria-labelledby="mw-toc-heading">'
1683 . Html::element( 'input', [
1684 'type' => 'checkbox',
1685 'role' => 'button',
1686 'id' => 'toctogglecheckbox',
1687 'class' => 'toctogglecheckbox',
1688 'style' => 'display:none',
1689 ] )
1690 . Html::openElement( 'div', [
1691 'class' => 'toctitle',
1692 'lang' => $lang->getHtmlCode(),
1693 'dir' => $lang->getDir(),
1694 ] )
1695 . '<h2 id="mw-toc-heading">' . $title . '</h2>'
1696 . '<span class="toctogglespan">'
1697 . Html::label( '', 'toctogglecheckbox', [
1698 'class' => 'toctogglelabel',
1699 ] )
1700 . '</span>'
1701 . "</div>"
1702 . $toc
1703 . "</ul>\n</div>\n";
1704 }
1705
1714 public static function generateTOC( $tree, Language $lang = null ) {
1715 $toc = '';
1716 $lastLevel = 0;
1717 foreach ( $tree as $section ) {
1718 if ( $section['toclevel'] > $lastLevel ) {
1719 $toc .= self::tocIndent();
1720 } elseif ( $section['toclevel'] < $lastLevel ) {
1721 $toc .= self::tocUnindent(
1722 $lastLevel - $section['toclevel'] );
1723 } else {
1724 $toc .= self::tocLineEnd();
1725 }
1726
1727 $toc .= self::tocLine( $section['anchor'],
1728 $section['line'], $section['number'],
1729 $section['toclevel'], $section['index'] );
1730 $lastLevel = $section['toclevel'];
1731 }
1732 $toc .= self::tocLineEnd();
1733 return self::tocList( $toc, $lang );
1734 }
1735
1752 public static function makeHeadline( $level, $attribs, $anchor, $html,
1753 $link, $fallbackAnchor = false
1754 ) {
1755 $anchorEscaped = htmlspecialchars( $anchor, ENT_COMPAT );
1756 $fallback = '';
1757 if ( $fallbackAnchor !== false && $fallbackAnchor !== $anchor ) {
1758 $fallbackAnchor = htmlspecialchars( $fallbackAnchor, ENT_COMPAT );
1759 $fallback = "<span id=\"$fallbackAnchor\"></span>";
1760 }
1761 return "<h$level$attribs"
1762 . "$fallback<span class=\"mw-headline\" id=\"$anchorEscaped\">$html</span>"
1763 . $link
1764 . "</h$level>";
1765 }
1766
1773 public static function splitTrail( $trail ) {
1774 $regex = MediaWikiServices::getInstance()->getContentLanguage()->linkTrail();
1775 $inside = '';
1776 if ( $trail !== '' && preg_match( $regex, $trail, $m ) ) {
1777 list( , $inside, $trail ) = $m;
1778 }
1779 return [ $inside, $trail ];
1780 }
1781
1811 public static function generateRollback(
1812 RevisionRecord $revRecord,
1813 IContextSource $context = null,
1814 $options = [ 'verify' ]
1815 ) {
1816 if ( $context === null ) {
1817 $context = RequestContext::getMain();
1818 }
1819
1820 $editCount = false;
1821 if ( in_array( 'verify', $options, true ) ) {
1822 $editCount = self::getRollbackEditCount( $revRecord, true );
1823 if ( $editCount === false ) {
1824 return '';
1825 }
1826 }
1827
1828 $inner = self::buildRollbackLink( $revRecord, $context, $editCount );
1829
1830 // Allow extensions to modify the rollback link.
1831 // Abort further execution if the extension wants full control over the link.
1832 if ( !Hooks::runner()->onLinkerGenerateRollbackLink(
1833 $revRecord, $context, $options, $inner ) ) {
1834 return $inner;
1835 }
1836
1837 if ( !in_array( 'noBrackets', $options, true ) ) {
1838 $inner = $context->msg( 'brackets' )->rawParams( $inner )->escaped();
1839 }
1840
1841 if ( MediaWikiServices::getInstance()->getUserOptionsLookup()
1842 ->getBoolOption( $context->getUser(), 'showrollbackconfirmation' )
1843 ) {
1844 $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
1845 $stats->increment( 'rollbackconfirmation.event.load' );
1846 $context->getOutput()->addModules( 'mediawiki.misc-authed-curate' );
1847 }
1848
1849 return '<span class="mw-rollback-link">' . $inner . '</span>';
1850 }
1851
1870 public static function getRollbackEditCount( RevisionRecord $revRecord, $verify ) {
1871 $showRollbackEditCount = MediaWikiServices::getInstance()->getMainConfig()->get( 'ShowRollbackEditCount' );
1872
1873 if ( !is_int( $showRollbackEditCount ) || !$showRollbackEditCount > 0 ) {
1874 // Nothing has happened, indicate this by returning 'null'
1875 return null;
1876 }
1877
1878 $dbr = wfGetDB( DB_REPLICA );
1879
1880 // Up to the value of $wgShowRollbackEditCount revisions are counted
1881 $revQuery = MediaWikiServices::getInstance()->getRevisionStore()->getQueryInfo();
1882 // T270033 Index renaming
1883 $revIndex = $dbr->indexExists( 'revision', 'page_timestamp', __METHOD__ )
1884 ? 'page_timestamp'
1885 : 'rev_page_timestamp';
1886 $res = $dbr->select(
1887 $revQuery['tables'],
1888 [ 'rev_user_text' => $revQuery['fields']['rev_user_text'], 'rev_deleted' ],
1889 [ 'rev_page' => $revRecord->getPageId() ],
1890 __METHOD__,
1891 [
1892 'USE INDEX' => [ 'revision' => $revIndex ],
1893 'ORDER BY' => [ 'rev_timestamp DESC', 'rev_id DESC' ],
1894 'LIMIT' => $showRollbackEditCount + 1
1895 ],
1896 $revQuery['joins']
1897 );
1898
1899 $revUser = $revRecord->getUser( RevisionRecord::RAW );
1900 $revUserText = $revUser ? $revUser->getName() : '';
1901
1902 $editCount = 0;
1903 $moreRevs = false;
1904 foreach ( $res as $row ) {
1905 if ( $row->rev_user_text != $revUserText ) {
1906 if ( $verify &&
1907 ( $row->rev_deleted & RevisionRecord::DELETED_TEXT
1908 || $row->rev_deleted & RevisionRecord::DELETED_USER
1909 ) ) {
1910 // If the user or the text of the revision we might rollback
1911 // to is deleted in some way we can't rollback. Similar to
1912 // the checks in WikiPage::commitRollback.
1913 return false;
1914 }
1915 $moreRevs = true;
1916 break;
1917 }
1918 $editCount++;
1919 }
1920
1921 if ( $verify && $editCount <= $showRollbackEditCount && !$moreRevs ) {
1922 // We didn't find at least $wgShowRollbackEditCount revisions made by the current user
1923 // and there weren't any other revisions. That means that the current user is the only
1924 // editor, so we can't rollback
1925 return false;
1926 }
1927 return $editCount;
1928 }
1929
1944 public static function buildRollbackLink(
1945 RevisionRecord $revRecord,
1946 IContextSource $context = null,
1947 $editCount = false
1948 ) {
1949 $config = MediaWikiServices::getInstance()->getMainConfig();
1950 $showRollbackEditCount = $config->get( 'ShowRollbackEditCount' );
1951 $miserMode = $config->get( 'MiserMode' );
1952 // To config which pages are affected by miser mode
1953 $disableRollbackEditCountSpecialPage = [ 'Recentchanges', 'Watchlist' ];
1954
1955 if ( $context === null ) {
1956 $context = RequestContext::getMain();
1957 }
1958
1959 $title = $revRecord->getPageAsLinkTarget();
1960 $revUser = $revRecord->getUser();
1961 $revUserText = $revUser ? $revUser->getName() : '';
1962
1963 $query = [
1964 'action' => 'rollback',
1965 'from' => $revUserText,
1966 'token' => $context->getUser()->getEditToken( 'rollback' ),
1967 ];
1968
1969 $attrs = [
1970 'data-mw' => 'interface',
1971 'title' => $context->msg( 'tooltip-rollback' )->text()
1972 ];
1973
1974 $options = [ 'known', 'noclasses' ];
1975
1976 if ( $context->getRequest()->getBool( 'bot' ) ) {
1977 // T17999
1978 $query['hidediff'] = '1';
1979 $query['bot'] = '1';
1980 }
1981
1982 if ( $miserMode ) {
1983 foreach ( $disableRollbackEditCountSpecialPage as $specialPage ) {
1984 if ( $context->getTitle()->isSpecial( $specialPage ) ) {
1985 $showRollbackEditCount = false;
1986 break;
1987 }
1988 }
1989 }
1990
1991 // The edit count can be 0 on replica lag, fall back to the generic rollbacklink message
1992 $msg = [ 'rollbacklink' ];
1993 if ( is_int( $showRollbackEditCount ) && $showRollbackEditCount > 0 ) {
1994 if ( !is_numeric( $editCount ) ) {
1995 $editCount = self::getRollbackEditCount( $revRecord, false );
1996 }
1997
1998 if ( $editCount > $showRollbackEditCount ) {
1999 $msg = [ 'rollbacklinkcount-morethan', Message::numParam( $showRollbackEditCount ) ];
2000 } elseif ( $editCount ) {
2001 $msg = [ 'rollbacklinkcount', Message::numParam( $editCount ) ];
2002 }
2003 }
2004
2005 $html = $context->msg( ...$msg )->parse();
2006 return self::link( $title, $html, $attrs, $query, $options );
2007 }
2008
2017 public static function formatHiddenCategories( $hiddencats ) {
2018 $outText = '';
2019 if ( count( $hiddencats ) > 0 ) {
2020 # Construct the HTML
2021 $outText = '<div class="mw-hiddenCategoriesExplanation">';
2022 $outText .= wfMessage( 'hiddencategories' )->numParams( count( $hiddencats ) )->parseAsBlock();
2023 $outText .= "</div><ul>\n";
2024
2025 foreach ( $hiddencats as $titleObj ) {
2026 # If it's hidden, it must exist - no need to check with a LinkBatch
2027 $outText .= '<li>'
2028 . self::link( $titleObj, null, [], [], 'known' )
2029 . "</li>\n";
2030 }
2031 $outText .= '</ul>';
2032 }
2033 return $outText;
2034 }
2035
2039 private static function getContextFromMain() {
2040 $context = RequestContext::getMain();
2041 $context = new DerivativeContext( $context );
2042 return $context;
2043 }
2044
2062 public static function titleAttrib( $name, $options = null, array $msgParams = [], $localizer = null ) {
2063 if ( !$localizer ) {
2064 $localizer = self::getContextFromMain();
2065 }
2066 $message = $localizer->msg( "tooltip-$name", $msgParams );
2067 if ( $message->isDisabled() ) {
2068 $tooltip = false;
2069 } else {
2070 $tooltip = $message->text();
2071 # Compatibility: formerly some tooltips had [alt-.] hardcoded
2072 $tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip );
2073 }
2074
2075 $options = (array)$options;
2076
2077 if ( in_array( 'nonexisting', $options ) ) {
2078 $tooltip = $localizer->msg( 'red-link-title', $tooltip ?: '' )->text();
2079 }
2080 if ( in_array( 'withaccess', $options ) ) {
2081 $accesskey = self::accesskey( $name, $localizer );
2082 if ( $accesskey !== false ) {
2083 // Should be build the same as in jquery.accessKeyLabel.js
2084 if ( $tooltip === false || $tooltip === '' ) {
2085 $tooltip = $localizer->msg( 'brackets', $accesskey )->text();
2086 } else {
2087 $tooltip .= $localizer->msg( 'word-separator' )->text();
2088 $tooltip .= $localizer->msg( 'brackets', $accesskey )->text();
2089 }
2090 }
2091 }
2092
2093 return $tooltip;
2094 }
2095
2096 public static $accesskeycache;
2097
2110 public static function accesskey( $name, $localizer = null ) {
2111 if ( !isset( self::$accesskeycache[$name] ) ) {
2112 if ( !$localizer ) {
2113 $localizer = self::getContextFromMain();
2114 }
2115 $msg = $localizer->msg( "accesskey-$name" );
2116 self::$accesskeycache[$name] = $msg->isDisabled() ? false : $msg->plain();
2117 }
2118 return self::$accesskeycache[$name];
2119 }
2120
2135 public static function getRevDeleteLink(
2136 Authority $performer,
2137 RevisionRecord $revRecord,
2139 ) {
2140 $canHide = $performer->isAllowed( 'deleterevision' );
2141 $canHideHistory = $performer->isAllowed( 'deletedhistory' );
2142 if ( !$canHide && !( $revRecord->getVisibility() && $canHideHistory ) ) {
2143 return '';
2144 }
2145
2146 if ( !$revRecord->userCan( RevisionRecord::DELETED_RESTRICTED, $performer ) ) {
2147 return self::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
2148 }
2149 $prefixedDbKey = MediaWikiServices::getInstance()->getTitleFormatter()->
2150 getPrefixedDBkey( $title );
2151 if ( $revRecord->getId() ) {
2152 // RevDelete links using revision ID are stable across
2153 // page deletion and undeletion; use when possible.
2154 $query = [
2155 'type' => 'revision',
2156 'target' => $prefixedDbKey,
2157 'ids' => $revRecord->getId()
2158 ];
2159 } else {
2160 // Older deleted entries didn't save a revision ID.
2161 // We have to refer to these by timestamp, ick!
2162 $query = [
2163 'type' => 'archive',
2164 'target' => $prefixedDbKey,
2165 'ids' => $revRecord->getTimestamp()
2166 ];
2167 }
2168 return self::revDeleteLink(
2169 $query,
2170 $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ),
2171 $canHide
2172 );
2173 }
2174
2187 public static function revDeleteLink( $query = [], $restricted = false, $delete = true ) {
2188 $sp = SpecialPage::getTitleFor( 'Revisiondelete' );
2189 $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
2190 $html = wfMessage( $msgKey )->escaped();
2191 $tag = $restricted ? 'strong' : 'span';
2192 $link = self::link( $sp, $html, [], $query, [ 'known', 'noclasses' ] );
2193 return Xml::tags(
2194 $tag,
2195 [ 'class' => 'mw-revdelundel-link' ],
2196 wfMessage( 'parentheses' )->rawParams( $link )->escaped()
2197 );
2198 }
2199
2211 public static function revDeleteLinkDisabled( $delete = true ) {
2212 $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
2213 $html = wfMessage( $msgKey )->escaped();
2214 $htmlParentheses = wfMessage( 'parentheses' )->rawParams( $html )->escaped();
2215 return Xml::tags( 'span', [ 'class' => 'mw-revdelundel-link' ], $htmlParentheses );
2216 }
2217
2229 private static function updateWatchstarTooltipMessage(
2230 string &$tooltip, array &$msgParams, $config, $user, $relevantTitle
2231 ): void {
2232 if ( !$config || !$user || !$relevantTitle ) {
2233 $mainContext = self::getContextFromMain();
2234 if ( !$config ) {
2235 $config = $mainContext->getConfig();
2236 }
2237 if ( !$user ) {
2238 $user = $mainContext->getUser();
2239 }
2240 if ( !$relevantTitle ) {
2241 $relevantTitle = $mainContext->getSkin()->getRelevantTitle();
2242 }
2243 }
2244
2245 $isWatchlistExpiryEnabled = $config->get( 'WatchlistExpiry' );
2246 if ( !$isWatchlistExpiryEnabled ) {
2247 return;
2248 }
2249 $watchStore = MediaWikiServices::getInstance()->getWatchedItemStore();
2250 $watchedItem = $watchStore->getWatchedItem( $user, $relevantTitle );
2251 if ( $watchedItem instanceof WatchedItem && $watchedItem->getExpiry() !== null ) {
2252 $diffInDays = $watchedItem->getExpiryInDays();
2253
2254 if ( $diffInDays ) {
2255 $msgParams = [ $diffInDays ];
2256 // Resolves to tooltip-ca-unwatch-expiring message
2257 $tooltip = 'ca-unwatch-expiring';
2258 } else { // Resolves to tooltip-ca-unwatch-expiring-hours message
2259 $tooltip = 'ca-unwatch-expiring-hours';
2260 }
2261 }
2262 }
2263
2280 public static function tooltipAndAccesskeyAttribs(
2281 $name,
2282 array $msgParams = [],
2283 $options = null,
2284 $localizer = null,
2285 $user = null,
2286 $config = null,
2287 $relevantTitle = null
2288 ) {
2289 $options = (array)$options;
2290 $options[] = 'withaccess';
2291 $tooltipTitle = $name;
2292
2293 // Get optional parameters from global context if any missing.
2294 if ( !$localizer ) {
2295 $localizer = self::getContextFromMain();
2296 }
2297
2298 // @since 1.35 - add a WatchlistExpiry feature flag to show new watchstar tooltip message
2299 if ( $name === 'ca-unwatch' ) {
2300 self::updateWatchstarTooltipMessage( $tooltipTitle, $msgParams, $config, $user, $relevantTitle );
2301 }
2302
2303 $attribs = [
2304 'title' => self::titleAttrib( $tooltipTitle, $options, $msgParams, $localizer ),
2305 'accesskey' => self::accesskey( $name, $localizer )
2306 ];
2307 if ( $attribs['title'] === false ) {
2308 unset( $attribs['title'] );
2309 }
2310 if ( $attribs['accesskey'] === false ) {
2311 unset( $attribs['accesskey'] );
2312 }
2313 return $attribs;
2314 }
2315
2323 public static function tooltip( $name, $options = null ) {
2324 $tooltip = self::titleAttrib( $name, $options );
2325 if ( $tooltip === false ) {
2326 return '';
2327 }
2328 return Xml::expandAttributes( [
2329 'title' => $tooltip
2330 ] );
2331 }
2332
2333}
UserOptionsLookup $userOptionsLookup
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.
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
if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) $wgLang
Definition Setup.php:927
if(!defined( 'MW_NO_SESSION') &&! $wgCommandLineMode) $wgTitle
Definition Setup.php:927
if(!defined('MW_SETUP_CALLBACK'))
Expand dynamic defaults and shortcuts.
Definition WebStart.php:89
An IContextSource implementation which will inherit context from another source but allow individual ...
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:30
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition Language.php:44
Some internal bits split of from Skin.php.
Definition Linker.php:39
static accesskey( $name, $localizer=null)
Given the id of an interface element, constructs the appropriate accesskey attribute from the system ...
Definition Linker.php:2110
static makeMediaLinkFile(LinkTarget $title, $file, $html='')
Create a direct link to a given uploaded file.
Definition Linker.php:972
static link( $target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition Linker.php:88
static fnamePart( $url)
Returns the filename part of an url.
Definition Linker.php:221
static tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition Linker.php:1641
static getContextFromMain()
Definition Linker.php:2039
static userLink( $userId, $userName, $altUserName=false)
Make user link (or user contributions for unregistered users)
Definition Linker.php:1093
static $accesskeycache
Definition Linker.php:2096
static expandLocalLinks(string $html)
Helper function to expand local links.
Definition Linker.php:1391
static getRevisionDeletedClass(RevisionRecord $revisionRecord)
Returns css class of a deleted revision.
Definition Linker.php:1328
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:1014
static getRollbackEditCount(RevisionRecord $revRecord, $verify)
This function will return the number of revisions which a rollback would revert and,...
Definition Linker.php:1870
static linkKnown( $target, $html=null, $customAttribs=[], $query=[], $options=[ 'known'])
Identical to link(), except $options defaults to 'known'.
Definition Linker.php:140
static makeExternalImage( $url, $alt='')
Return the code for images which were added via external links, via Parser::maybeMakeExternalImage().
Definition Linker.php:241
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:1584
static revUserLink(RevisionRecord $revRecord, $isPublic=false)
Generate a user link if the current user is allowed to view it.
Definition Linker.php:1300
static processResponsiveImages( $file, $thumb, $hp)
Process responsive images: add 1.5x and 2x subimages to the thumbnail, where applicable.
Definition Linker.php:817
static blockLink( $userId, $userText)
Definition Linker.php:1253
static revDeleteLinkDisabled( $delete=true)
Creates a dead (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2211
static normalizeSubpageLink( $contextTitle, $target, &$text)
Definition Linker.php:1465
const TOOL_LINKS_NOBLOCK
Flags for userToolLinks()
Definition Linker.php:43
static makeBrokenImageLinkObj( $title, $label='', $query='', $unused1='', $unused2='', $time=false, array $handlerParams=[])
Make a "broken" link to an image.
Definition Linker.php:853
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:2135
static makeSelfLinkObj( $nt, $html='', $query='', $trail='', $prefix='')
Make appropriate markup for a link to the current article.
Definition Linker.php:162
static getUploadUrl( $destFile, $query='')
Get the URL to upload a certain file.
Definition Linker.php:922
static tocIndent()
Add another level to the Table of Contents.
Definition Linker.php:1615
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:297
static updateWatchstarTooltipMessage(string &$tooltip, array &$msgParams, $config, $user, $relevantTitle)
Updates the tooltip message and its parameters if watchlist expiry is enabled.
Definition Linker.php:2229
static getInvalidTitleDescription(IContextSource $context, $namespace, $title)
Get a message saying that an invalid title was encountered.
Definition Linker.php:186
static emailLink( $userId, $userText)
Definition Linker.php:1274
static generateRollback(RevisionRecord $revRecord, IContextSource $context=null, $options=[ 'verify'])
Generate a rollback link for a given revision.
Definition Linker.php:1811
static formatHiddenCategories( $hiddencats)
Returns HTML for the "hidden categories on this page" list.
Definition Linker.php:2017
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:1773
static generateTOC( $tree, Language $lang=null)
Generate a table of contents from a section tree.
Definition Linker.php:1714
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:2062
const TOOL_LINKS_EMAIL
Definition Linker.php:44
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:576
static formatRevisionSize( $size)
Definition Linker.php:1600
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:1562
static tooltip( $name, $options=null)
Returns raw bits of HTML, use titleAttrib()
Definition Linker.php:2323
static userTalkLink( $userId, $userText)
Definition Linker.php:1231
static makeThumbLink2(LinkTarget $title, $file, $frameParams=[], $handlerParams=[], $time=false, $query="", array $classes=[], ?Parser $parser=null)
Definition Linker.php:608
static buildRollbackLink(RevisionRecord $revRecord, IContextSource $context=null, $editCount=false)
Build a raw rollback link, useful for collections of "tool" links.
Definition Linker.php:1944
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:1348
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:1452
static makeMediaLinkObj( $title, $html='', $time=false)
Create a direct link to a given uploaded file.
Definition Linker.php:953
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition Linker.php:1040
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:1138
static normaliseSpecialPage(LinkTarget $target)
Definition Linker.php:207
static makeHeadline( $level, $attribs, $anchor, $html, $link, $fallbackAnchor=false)
Create a headline for content.
Definition Linker.php:1752
static tocUnindent( $level)
Finish one or more sublevels on the Table of Contents.
Definition Linker.php:1626
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:1677
static getImageLinkMTOParams( $frameParams, $query='', $parser=null)
Get the link parameters for MediaTransformOutput::toHtml() from given frame parameters supplied by th...
Definition Linker.php:533
static revDeleteLink( $query=[], $restricted=false, $delete=true)
Creates a (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2187
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:2280
static tocLineEnd()
End a Table Of Contents line.
Definition Linker.php:1665
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:1426
static userToolLinksRedContribs( $userId, $userText, $edits=null, $useParentheses=true)
Alias for userToolLinks( $userId, $userText, true );.
Definition Linker.php:1219
MediaWikiServices is the service locator for the application scope of MediaWiki.
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.
static numParam( $num)
Definition Message.php:1163
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition Parser.php:92
getTargetLanguage()
Get the target language for the content being parsed.
Definition Parser.php:1155
getTitle()
Definition Parser.php:1008
getBadFileLookup()
Get the BadFileLookup instance that this Parser is using.
Definition Parser.php:1229
static getExternalLinkRel( $url=false, LinkTarget $title=null)
Get the rel attribute for a particular external link.
Definition Parser.php:2230
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 newFromId( $id)
Static factory method for creation from a given user ID.
Definition User.php:637
Representation of a pair of user and title for watchlist entries.
getExpiry(?int $style=TS_MW)
When the watched item will expire.
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.
msg( $key,... $params)
This is the method for getting translated interface messages.
foreach( $mmfl['setupFiles'] as $fileName) if($queue) if(empty( $mmfl['quiet'])) $s
const DB_REPLICA
Definition defines.php:25
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