MediaWiki REL1_37
Linker.php
Go to the documentation of this file.
1<?php
27use Wikimedia\IPUtils;
28
38class Linker {
42 public const TOOL_LINKS_NOBLOCK = 1;
43 public const TOOL_LINKS_EMAIL = 2;
44
87 public static function link(
88 $target, $html = null, $customAttribs = [], $query = [], $options = []
89 ) {
90 if ( !$target instanceof LinkTarget ) {
91 wfWarn( __METHOD__ . ': Requires $target to be a LinkTarget object.', 2 );
92 return "<!-- ERROR -->$html";
93 }
94
95 $services = MediaWikiServices::getInstance();
96 $options = (array)$options;
97 if ( $options ) {
98 // Custom options, create new LinkRenderer
99 $linkRenderer = $services->getLinkRendererFactory()
100 ->createFromLegacyOptions( $options );
101 } else {
102 $linkRenderer = $services->getLinkRenderer();
103 }
104
105 if ( $html !== null ) {
106 $text = new HtmlArmor( $html );
107 } else {
108 $text = null;
109 }
110
111 if ( in_array( 'known', $options, true ) ) {
112 return $linkRenderer->makeKnownLink( $target, $text, $customAttribs, $query );
113 }
114
115 if ( in_array( 'broken', $options, true ) ) {
116 return $linkRenderer->makeBrokenLink( $target, $text, $customAttribs, $query );
117 }
118
119 if ( in_array( 'noclasses', $options, true ) ) {
120 return $linkRenderer->makePreloadedLink( $target, $text, '', $customAttribs, $query );
121 }
122
123 return $linkRenderer->makeLink( $target, $text, $customAttribs, $query );
124 }
125
139 public static function linkKnown(
140 $target, $html = null, $customAttribs = [],
141 $query = [], $options = [ 'known' ]
142 ) {
143 return self::link( $target, $html, $customAttribs, $query, $options );
144 }
145
161 public static function makeSelfLinkObj( $nt, $html = '', $query = '', $trail = '', $prefix = '' ) {
162 $nt = Title::newFromLinkTarget( $nt );
163 $ret = "<a class=\"mw-selflink selflink\">{$prefix}{$html}</a>{$trail}";
164 if ( !Hooks::runner()->onSelfLinkBegin( $nt, $html, $trail, $prefix, $ret ) ) {
165 return $ret;
166 }
167
168 if ( $html == '' ) {
169 $html = htmlspecialchars( $nt->getPrefixedText() );
170 }
171 list( $inside, $trail ) = self::splitTrail( $trail );
172 return "<a class=\"mw-selflink selflink\">{$prefix}{$html}{$inside}</a>{$trail}";
173 }
174
185 public static function getInvalidTitleDescription( IContextSource $context, $namespace, $title ) {
186 // First we check whether the namespace exists or not.
187 if ( MediaWikiServices::getInstance()->getNamespaceInfo()->exists( $namespace ) ) {
188 if ( $namespace == NS_MAIN ) {
189 $name = $context->msg( 'blanknamespace' )->text();
190 } else {
191 $name = MediaWikiServices::getInstance()->getContentLanguage()->
192 getFormattedNsText( $namespace );
193 }
194 return $context->msg( 'invalidtitle-knownnamespace', $namespace, $name, $title )->text();
195 }
196
197 return $context->msg( 'invalidtitle-unknownnamespace', $namespace, $title )->text();
198 }
199
206 public static function normaliseSpecialPage( LinkTarget $target ) {
207 wfDeprecated( __METHOD__, '1.35' );
208 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
209 return $linkRenderer->normalizeTarget( $target );
210 }
211
220 private static function fnamePart( $url ) {
221 $basename = strrchr( $url, '/' );
222 if ( $basename === false ) {
223 $basename = $url;
224 } else {
225 $basename = substr( $basename, 1 );
226 }
227 return $basename;
228 }
229
240 public static function makeExternalImage( $url, $alt = '' ) {
241 if ( $alt == '' ) {
242 $alt = self::fnamePart( $url );
243 }
244 $img = '';
245 $success = Hooks::runner()->onLinkerMakeExternalImage( $url, $alt, $img );
246 if ( !$success ) {
247 wfDebug( "Hook LinkerMakeExternalImage changed the output of external image "
248 . "with url {$url} and alt text {$alt} to {$img}" );
249 return $img;
250 }
251 return Html::element( 'img',
252 [
253 'src' => $url,
254 'alt' => $alt
255 ]
256 );
257 }
258
296 public static function makeImageLink( Parser $parser, LinkTarget $title,
297 $file, $frameParams = [], $handlerParams = [], $time = false,
298 $query = "", $widthOption = null
299 ) {
300 $title = Title::newFromLinkTarget( $title );
301 $res = null;
302 $dummy = new DummyLinker;
303 if ( !Hooks::runner()->onImageBeforeProduceHTML( $dummy, $title,
304 $file, $frameParams, $handlerParams, $time, $res,
305 $parser, $query, $widthOption )
306 ) {
307 return $res;
308 }
309
310 if ( $file && !$file->allowInlineDisplay() ) {
311 wfDebug( __METHOD__ . ': ' . $title->getPrefixedDBkey() . " does not allow inline display" );
312 return self::link( $title );
313 }
314
315 // Clean up parameters
316 $page = $handlerParams['page'] ?? false;
317 if ( !isset( $frameParams['align'] ) ) {
318 $frameParams['align'] = '';
319 }
320 if ( !isset( $frameParams['alt'] ) ) {
321 $frameParams['alt'] = '';
322 }
323 if ( !isset( $frameParams['title'] ) ) {
324 $frameParams['title'] = '';
325 }
326 if ( !isset( $frameParams['class'] ) ) {
327 $frameParams['class'] = '';
328 }
329
330 $services = MediaWikiServices::getInstance();
331 $enableLegacyMediaDOM = $services->getMainConfig()->get( 'ParserEnableLegacyMediaDOM' );
332
333 $classes = [];
334 if ( !isset( $handlerParams['width'] ) ) {
335 $classes[] = 'mw-default-size';
336 }
337
338 $prefix = $postfix = '';
339
340 if ( $enableLegacyMediaDOM ) {
341 if ( $frameParams['align'] == 'center' ) {
342 $prefix = '<div class="center">';
343 $postfix = '</div>';
344 $frameParams['align'] = 'none';
345 }
346 }
347
348 if ( $file && !isset( $handlerParams['width'] ) ) {
349 if ( isset( $handlerParams['height'] ) && $file->isVectorized() ) {
350 // If its a vector image, and user only specifies height
351 // we don't want it to be limited by its "normal" width.
352 global $wgSVGMaxSize;
353 $handlerParams['width'] = $wgSVGMaxSize;
354 } else {
355 $handlerParams['width'] = $file->getWidth( $page );
356 }
357
358 if ( isset( $frameParams['thumbnail'] )
359 || isset( $frameParams['manualthumb'] )
360 || isset( $frameParams['framed'] )
361 || isset( $frameParams['frameless'] )
362 || !$handlerParams['width']
363 ) {
365
366 if ( $widthOption === null || !isset( $wgThumbLimits[$widthOption] ) ) {
367 $userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
368 $widthOption = $userOptionsLookup->getDefaultOption( 'thumbsize' );
369 }
370
371 // Reduce width for upright images when parameter 'upright' is used
372 if ( isset( $frameParams['upright'] ) && $frameParams['upright'] == 0 ) {
373 $frameParams['upright'] = $wgThumbUpright;
374 }
375
376 // For caching health: If width scaled down due to upright
377 // parameter, round to full __0 pixel to avoid the creation of a
378 // lot of odd thumbs.
379 $prefWidth = isset( $frameParams['upright'] ) ?
380 round( $wgThumbLimits[$widthOption] * $frameParams['upright'], -1 ) :
381 $wgThumbLimits[$widthOption];
382
383 // Use width which is smaller: real image width or user preference width
384 // Unless image is scalable vector.
385 if ( !isset( $handlerParams['height'] ) && ( $handlerParams['width'] <= 0 ||
386 $prefWidth < $handlerParams['width'] || $file->isVectorized() ) ) {
387 $handlerParams['width'] = $prefWidth;
388 }
389 }
390 }
391
392 if ( isset( $frameParams['thumbnail'] ) || isset( $frameParams['manualthumb'] )
393 || isset( $frameParams['framed'] )
394 ) {
395 if ( $enableLegacyMediaDOM ) {
396 // This is no longer needed in our new media output, since the
397 // default styling in content.media.less takes care of it;
398 // see T269704.
399
400 # Create a thumbnail. Alignment depends on the writing direction of
401 # the page content language (right-aligned for LTR languages,
402 # left-aligned for RTL languages)
403 # If a thumbnail width has not been provided, it is set
404 # to the default user option as specified in Language*.php
405 if ( $frameParams['align'] == '' ) {
406 $frameParams['align'] = $parser->getTargetLanguage()->alignEnd();
407 }
408 }
409 return $prefix .
410 self::makeThumbLink2( $title, $file, $frameParams, $handlerParams, $time, $query, $classes ) .
411 $postfix;
412 }
413
414 switch ( $file ? $file->getMediaType() : '' ) {
415 case 'AUDIO':
416 $rdfaType = 'mw:Audio';
417 break;
418 case 'VIDEO':
419 $rdfaType = 'mw:Video';
420 break;
421 default:
422 $rdfaType = 'mw:Image';
423 }
424
425 if ( $file && isset( $frameParams['frameless'] ) ) {
426 $rdfaType .= '/Frameless';
427 $srcWidth = $file->getWidth( $page );
428 # For "frameless" option: do not present an image bigger than the
429 # source (for bitmap-style images). This is the same behavior as the
430 # "thumb" option does it already.
431 if ( $srcWidth && !$file->mustRender() && $handlerParams['width'] > $srcWidth ) {
432 $handlerParams['width'] = $srcWidth;
433 }
434 }
435
436 if ( $file && isset( $handlerParams['width'] ) ) {
437 # Create a resized image, without the additional thumbnail features
438 $thumb = $file->transform( $handlerParams );
439 } else {
440 $thumb = false;
441 }
442
443 if ( !$thumb ) {
444 $rdfaType = 'mw:Error ' . $rdfaType;
445 $label = '';
446 if ( $enableLegacyMediaDOM ) {
447 // This is the information for tooltips for inline images which
448 // Parsoid stores in data-mw. See T273014
449 $label = $frameParams['title'];
450 }
452 $title, $label, '', '', '', (bool)$time, $handlerParams
453 );
454 } else {
455 self::processResponsiveImages( $file, $thumb, $handlerParams );
456 $params = [
457 'alt' => $frameParams['alt'],
458 'title' => $frameParams['title'],
459 ];
460 if ( $enableLegacyMediaDOM ) {
461 $params += [
462 'valign' => $frameParams['valign'] ?? false,
463 'img-class' => $frameParams['class'],
464 ];
465 if ( isset( $frameParams['border'] ) ) {
466 $params['img-class'] .= ( $params['img-class'] !== '' ? ' ' : '' ) . 'thumbborder';
467 }
468 }
469 $params = self::getImageLinkMTOParams( $frameParams, $query, $parser ) + $params;
470 $s = $thumb->toHtml( $params );
471 }
472
473 if ( $enableLegacyMediaDOM ) {
474 if ( $frameParams['align'] != '' ) {
475 $s = Html::rawElement(
476 'div',
477 [ 'class' => 'float' . $frameParams['align'] ],
478 $s
479 );
480 }
481 return str_replace( "\n", ' ', $prefix . $s . $postfix );
482 }
483
484 $wrapper = 'span';
485 $caption = '';
486
487 if ( $frameParams['align'] != '' ) {
488 $wrapper = 'figure';
489 // Possible values: mw-halign-left mw-halign-center mw-halign-right mw-halign-none
490 $classes[] = "mw-halign-{$frameParams['align']}";
491 $caption = Html::rawElement(
492 'figcaption', [], $frameParams['caption'] ?? ''
493 );
494 } elseif ( isset( $frameParams['valign'] ) ) {
495 // Possible values: mw-valign-middle mw-valign-baseline mw-valign-sub
496 // mw-valign-super mw-valign-top mw-valign-text-top mw-valign-bottom
497 // mw-valign-text-bottom
498 $classes[] = "mw-valign-{$frameParams['valign']}";
499 }
500
501 if ( isset( $frameParams['border'] ) ) {
502 $classes[] = 'mw-image-border';
503 }
504
505 if ( isset( $frameParams['class'] ) ) {
506 $classes[] = $frameParams['class'];
507 }
508
509 $attribs = [
510 'class' => $classes,
511 'typeof' => $rdfaType,
512 ];
513
514 $s = Html::rawElement( $wrapper, $attribs, $s . $caption );
515
516 return str_replace( "\n", ' ', $s );
517 }
518
527 private static function getImageLinkMTOParams( $frameParams, $query = '', $parser = null ) {
528 $mtoParams = [];
529 if ( isset( $frameParams['link-url'] ) && $frameParams['link-url'] !== '' ) {
530 $mtoParams['custom-url-link'] = $frameParams['link-url'];
531 if ( isset( $frameParams['link-target'] ) ) {
532 $mtoParams['custom-target-link'] = $frameParams['link-target'];
533 }
534 if ( $parser ) {
535 $extLinkAttrs = $parser->getExternalLinkAttribs( $frameParams['link-url'] );
536 foreach ( $extLinkAttrs as $name => $val ) {
537 // Currently could include 'rel' and 'target'
538 $mtoParams['parser-extlink-' . $name] = $val;
539 }
540 }
541 } elseif ( isset( $frameParams['link-title'] ) && $frameParams['link-title'] !== '' ) {
542 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
543 $mtoParams['custom-title-link'] = Title::newFromLinkTarget(
544 $linkRenderer->normalizeTarget( $frameParams['link-title'] )
545 );
546 } elseif ( !empty( $frameParams['no-link'] ) ) {
547 // No link
548 } else {
549 $mtoParams['desc-link'] = true;
550 $mtoParams['desc-query'] = $query;
551 }
552 return $mtoParams;
553 }
554
567 public static function makeThumbLinkObj(
568 LinkTarget $title, $file, $label = '', $alt = '', $align = null,
569 $params = [], $framed = false, $manualthumb = ""
570 ) {
571 $frameParams = [
572 'alt' => $alt,
573 'caption' => $label,
574 'align' => $align
575 ];
576 if ( $framed ) {
577 $frameParams['framed'] = true;
578 }
579 if ( $manualthumb ) {
580 $frameParams['manualthumb'] = $manualthumb;
581 }
582 $classes = [ 'mw-default-size' ];
584 $title, $file, $frameParams, $params, false, '', $classes
585 );
586 }
587
598 public static function makeThumbLink2(
599 LinkTarget $title, $file, $frameParams = [], $handlerParams = [],
600 $time = false, $query = "", array $classes = []
601 ) {
602 $exists = $file && $file->exists();
603
604 $services = MediaWikiServices::getInstance();
605 $enableLegacyMediaDOM = $services->getMainConfig()->get( 'ParserEnableLegacyMediaDOM' );
606
607 $page = $handlerParams['page'] ?? false;
608 if ( !isset( $frameParams['align'] ) ) {
609 $frameParams['align'] = '';
610 if ( $enableLegacyMediaDOM ) {
611 $frameParams['align'] = 'right';
612 }
613 }
614 if ( !isset( $frameParams['alt'] ) ) {
615 $frameParams['alt'] = '';
616 }
617 if ( !isset( $frameParams['title'] ) ) {
618 $frameParams['title'] = '';
619 }
620 if ( !isset( $frameParams['caption'] ) ) {
621 $frameParams['caption'] = '';
622 }
623
624 if ( empty( $handlerParams['width'] ) ) {
625 // Reduce width for upright images when parameter 'upright' is used
626 $handlerParams['width'] = isset( $frameParams['upright'] ) ? 130 : 180;
627 }
628
629 $thumb = false;
630 $noscale = false;
631 $manualthumb = false;
632 $rdfaType = null;
633
634 if ( !$exists ) {
635 $outerWidth = $handlerParams['width'] + 2;
636 } else {
637 if ( isset( $frameParams['manualthumb'] ) ) {
638 # Use manually specified thumbnail
639 $manual_title = Title::makeTitleSafe( NS_FILE, $frameParams['manualthumb'] );
640 if ( $manual_title ) {
641 $manual_img = $services->getRepoGroup()
642 ->findFile( $manual_title );
643 if ( $manual_img ) {
644 $thumb = $manual_img->getUnscaledThumb( $handlerParams );
645 $manualthumb = true;
646 } else {
647 $exists = false;
648 }
649 }
650 } elseif ( isset( $frameParams['framed'] ) ) {
651 // Use image dimensions, don't scale
652 $thumb = $file->getUnscaledThumb( $handlerParams );
653 $noscale = true;
654 $rdfaType = '/Frame';
655 } else {
656 # Do not present an image bigger than the source, for bitmap-style images
657 # This is a hack to maintain compatibility with arbitrary pre-1.10 behavior
658 $srcWidth = $file->getWidth( $page );
659 if ( $srcWidth && !$file->mustRender() && $handlerParams['width'] > $srcWidth ) {
660 $handlerParams['width'] = $srcWidth;
661 }
662 $thumb = $file->transform( $handlerParams );
663 }
664
665 if ( $thumb ) {
666 $outerWidth = $thumb->getWidth() + 2;
667 } else {
668 $outerWidth = $handlerParams['width'] + 2;
669 }
670 }
671
672 $url = Title::newFromLinkTarget( $title )->getLocalURL( $query );
673
674 if ( $enableLegacyMediaDOM && $page ) {
675 # ThumbnailImage::toHtml() already adds page= onto the end of DjVu URLs
676 # So we don't need to pass it here in $query. However, the URL for the
677 # zoom icon still needs it, so we make a unique query for it. See T16771
678 $url = wfAppendQuery( $url, [ 'page' => $page ] );
679 }
680
681 if ( $manualthumb
682 && !isset( $frameParams['link-title'] )
683 && !isset( $frameParams['link-url'] )
684 && !isset( $frameParams['no-link'] ) ) {
685 $frameParams['link-url'] = $url;
686 }
687
688 if ( $frameParams['align'] != '' ) {
689 // Possible values: mw-halign-left mw-halign-center mw-halign-right mw-halign-none
690 $classes[] = "mw-halign-{$frameParams['align']}";
691 }
692
693 if ( isset( $frameParams['class'] ) ) {
694 $classes[] = $frameParams['class'];
695 }
696
697 $s = '';
698
699 if ( $enableLegacyMediaDOM ) {
700 $s .= "<div class=\"thumb t{$frameParams['align']}\">"
701 . "<div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
702 }
703
704 if ( !$exists ) {
705 $label = '';
706 if ( $enableLegacyMediaDOM ) {
707 // This is the information for tooltips for inline images which
708 // Parsoid stores in data-mw. See T273014
709 $label = $frameParams['title'];
710 }
712 $title, $label, '', '', '', (bool)$time, $handlerParams
713 );
714 $zoomIcon = '';
715 } elseif ( !$thumb ) {
716 // FIXME(T169975): Add "mw:Error"?
717 $s .= wfMessage( 'thumbnail_error', '' )->escaped();
718 $zoomIcon = '';
719 } else {
720 if ( !$noscale && !$manualthumb ) {
721 self::processResponsiveImages( $file, $thumb, $handlerParams );
722 }
723 $params = [
724 'alt' => $frameParams['alt'],
725 'title' => $frameParams['title'],
726 ];
727 if ( $enableLegacyMediaDOM ) {
728 $params += [
729 'img-class' => ( isset( $frameParams['class'] ) && $frameParams['class'] !== ''
730 ? $frameParams['class'] . ' '
731 : '' ) . 'thumbimage'
732 ];
733 }
734 $params = self::getImageLinkMTOParams( $frameParams, $query ) + $params;
735 $s .= $thumb->toHtml( $params );
736 if ( isset( $frameParams['framed'] ) ) {
737 $zoomIcon = "";
738 } else {
739 $zoomIcon = Html::rawElement( 'div', [ 'class' => 'magnify' ],
740 Html::rawElement( 'a', [
741 'href' => $url,
742 'class' => 'internal',
743 'title' => wfMessage( 'thumbnail-more' )->text() ],
744 "" ) );
745 }
746 }
747
748 if ( $enableLegacyMediaDOM ) {
749 $s .= ' <div class="thumbcaption">' . $zoomIcon . $frameParams['caption'] . "</div></div></div>";
750 return str_replace( "\n", ' ', $s );
751 }
752
753 $s .= Html::rawElement(
754 'figcaption', [], $frameParams['caption'] ?? ''
755 );
756
757 $rdfaType = $rdfaType ?: '/Thumb';
758
759 switch ( $file ? $file->getMediaType() : '' ) {
760 case 'AUDIO':
761 $rdfaType = 'mw:Audio' . $rdfaType;
762 break;
763 case 'VIDEO':
764 $rdfaType = 'mw:Video' . $rdfaType;
765 break;
766 default:
767 $rdfaType = 'mw:Image' . $rdfaType;
768 }
769
770 if ( !$exists ) {
771 $rdfaType = 'mw:Error ' . $rdfaType;
772 }
773
774 $attribs = [
775 'class' => $classes,
776 'typeof' => $rdfaType,
777 ];
778
779 $s = Html::rawElement( 'figure', $attribs, $s );
780
781 return str_replace( "\n", ' ', $s );
782 }
783
792 public static function processResponsiveImages( $file, $thumb, $hp ) {
793 global $wgResponsiveImages;
794 if ( $wgResponsiveImages && $thumb && !$thumb->isError() ) {
795 $hp15 = $hp;
796 $hp15['width'] = round( $hp['width'] * 1.5 );
797 $hp20 = $hp;
798 $hp20['width'] = $hp['width'] * 2;
799 if ( isset( $hp['height'] ) ) {
800 $hp15['height'] = round( $hp['height'] * 1.5 );
801 $hp20['height'] = $hp['height'] * 2;
802 }
803
804 $thumb15 = $file->transform( $hp15 );
805 $thumb20 = $file->transform( $hp20 );
806 if ( $thumb15 && !$thumb15->isError() && $thumb15->getUrl() !== $thumb->getUrl() ) {
807 $thumb->responsiveUrls['1.5'] = $thumb15->getUrl();
808 }
809 if ( $thumb20 && !$thumb20->isError() && $thumb20->getUrl() !== $thumb->getUrl() ) {
810 $thumb->responsiveUrls['2'] = $thumb20->getUrl();
811 }
812 }
813 }
814
828 public static function makeBrokenImageLinkObj(
829 $title, $label = '', $query = '', $unused1 = '', $unused2 = '',
830 $time = false, array $handlerParams = []
831 ) {
832 if ( !$title instanceof LinkTarget ) {
833 wfWarn( __METHOD__ . ': Requires $title to be a LinkTarget object.' );
834 return "<!-- ERROR -->" . htmlspecialchars( $label );
835 }
836
837 $title = Title::castFromLinkTarget( $title );
838
840 if ( $label == '' ) {
841 $label = $title->getPrefixedText();
842 }
843
844 $html = Html::element( 'span', [
845 // These data attributes are used to dynamically size the span, see T273013
846 'data-width' => $handlerParams['width'] ?? null,
847 'data-height' => $handlerParams['height'] ?? null,
848 ], $label );
849
850 $services = MediaWikiServices::getInstance();
851 if ( $services->getMainConfig()->get( 'ParserEnableLegacyMediaDOM' ) ) {
852 $html = htmlspecialchars( $label );
853 }
854
855 $repoGroup = $services->getRepoGroup();
856 $currentExists = $time
857 && $repoGroup->findFile( $title ) !== false;
858
860 && !$currentExists
861 ) {
862 if ( $repoGroup->getLocalRepo()->checkRedirect( $title ) ) {
863 // We already know it's a redirect, so mark it accordingly
864 return self::link(
865 $title,
866 $html,
867 [ 'class' => 'mw-redirect' ],
868 wfCgiToArray( $query ),
869 [ 'known', 'noclasses' ]
870 );
871 }
872 return Html::rawElement( 'a', [
873 'href' => self::getUploadUrl( $title, $query ),
874 'class' => 'new',
875 'title' => $title->getPrefixedText()
876 ], $html );
877 }
878 return self::link(
879 $title,
880 $html,
881 [],
882 wfCgiToArray( $query ),
883 [ 'known', 'noclasses' ]
884 );
885 }
886
895 protected static function getUploadUrl( $destFile, $query = '' ) {
897 $q = 'wpDestFile=' . Title::castFromLinkTarget( $destFile )->getPartialURL();
898 if ( $query != '' ) {
899 $q .= '&' . $query;
900 }
901
904 }
905
908 }
909
910 $upload = SpecialPage::getTitleFor( 'Upload' );
911
912 return $upload->getLocalURL( $q );
913 }
914
924 public static function makeMediaLinkObj( $title, $html = '', $time = false ) {
925 $img = MediaWikiServices::getInstance()->getRepoGroup()->findFile(
926 $title, [ 'time' => $time ]
927 );
928 return self::makeMediaLinkFile( $title, $img, $html );
929 }
930
943 public static function makeMediaLinkFile( LinkTarget $title, $file, $html = '' ) {
944 if ( $file && $file->exists() ) {
945 $url = $file->getUrl();
946 $class = 'internal';
947 } else {
948 $url = self::getUploadUrl( $title );
949 $class = 'new';
950 }
951
952 $alt = $title->getText();
953 if ( $html == '' ) {
954 $html = $alt;
955 }
956
957 $ret = '';
958 $attribs = [
959 'href' => $url,
960 'class' => $class,
961 'title' => $alt
962 ];
963
964 if ( !Hooks::runner()->onLinkerMakeMediaLinkFile(
965 Title::castFromLinkTarget( $title ), $file, $html, $attribs, $ret )
966 ) {
967 wfDebug( "Hook LinkerMakeMediaLinkFile changed the output of link "
968 . "with url {$url} and text {$html} to {$ret}" );
969 return $ret;
970 }
971
972 return Html::rawElement( 'a', $attribs, $html );
973 }
974
985 public static function specialLink( $name, $key = '' ) {
986 if ( $key == '' ) {
987 $key = strtolower( $name );
988 }
989
990 return self::linkKnown( SpecialPage::getTitleFor( $name ), wfMessage( $key )->escaped() );
991 }
992
1011 public static function makeExternalLink( $url, $text, $escape = true,
1012 $linktype = '', $attribs = [], $title = null
1013 ) {
1014 global $wgTitle;
1015 $class = "external";
1016 if ( $linktype ) {
1017 $class .= " $linktype";
1018 }
1019 if ( isset( $attribs['class'] ) && $attribs['class'] ) {
1020 $class .= " {$attribs['class']}";
1021 }
1022 $attribs['class'] = $class;
1023
1024 if ( $escape ) {
1025 $text = htmlspecialchars( $text );
1026 }
1027
1028 if ( !$title ) {
1029 $title = $wgTitle;
1030 }
1031 $newRel = Parser::getExternalLinkRel( $url, $title );
1032 if ( !isset( $attribs['rel'] ) || $attribs['rel'] === '' ) {
1033 $attribs['rel'] = $newRel;
1034 } elseif ( $newRel !== null ) {
1035 // Merge the rel attributes.
1036 $newRels = explode( ' ', $newRel );
1037 $oldRels = explode( ' ', $attribs['rel'] );
1038 $combined = array_unique( array_merge( $newRels, $oldRels ) );
1039 $attribs['rel'] = implode( ' ', $combined );
1040 }
1041 $link = '';
1042 $success = Hooks::runner()->onLinkerMakeExternalLink(
1043 $url, $text, $link, $attribs, $linktype );
1044 if ( !$success ) {
1045 wfDebug( "Hook LinkerMakeExternalLink changed the output of link "
1046 . "with url {$url} and text {$text} to {$link}" );
1047 return $link;
1048 }
1049 $attribs['href'] = $url;
1050 return Html::rawElement( 'a', $attribs, $text );
1051 }
1052
1064 public static function userLink( $userId, $userName, $altUserName = false ) {
1065 if ( $userName === '' || $userName === false || $userName === null ) {
1066 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1067 'that need to be fixed?' );
1068 return wfMessage( 'empty-username' )->parse();
1069 }
1070
1071 $classes = 'mw-userlink';
1072 $page = null;
1073 if ( $userId == 0 ) {
1074 $page = ExternalUserNames::getUserLinkTitle( $userName );
1075
1076 if ( ExternalUserNames::isExternal( $userName ) ) {
1077 $classes .= ' mw-extuserlink';
1078 } elseif ( $altUserName === false ) {
1079 $altUserName = IPUtils::prettifyIP( $userName );
1080 }
1081 $classes .= ' mw-anonuserlink'; // Separate link class for anons (T45179)
1082 } else {
1083 $page = TitleValue::tryNew( NS_USER, strtr( $userName, ' ', '_' ) );
1084 }
1085
1086 // Wrap the output with <bdi> tags for directionality isolation
1087 $linkText =
1088 '<bdi>' . htmlspecialchars( $altUserName !== false ? $altUserName : $userName ) . '</bdi>';
1089
1090 return $page
1091 ? self::link( $page, $linkText, [ 'class' => $classes ] )
1092 : Html::rawElement( 'span', [ 'class' => $classes ], $linkText );
1093 }
1094
1109 public static function userToolLinks(
1110 $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits = null,
1111 $useParentheses = true
1112 ) {
1113 if ( $userText === '' ) {
1114 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1115 'that need to be fixed?' );
1116 return ' ' . wfMessage( 'empty-username' )->parse();
1117 }
1118
1120 $talkable = !( $wgDisableAnonTalk && $userId == 0 );
1121 $blockable = !( $flags & self::TOOL_LINKS_NOBLOCK );
1122 $addEmailLink = $flags & self::TOOL_LINKS_EMAIL && $userId;
1123
1124 if ( $userId == 0 && ExternalUserNames::isExternal( $userText ) ) {
1125 // No tools for an external user
1126 return '';
1127 }
1128
1129 $items = [];
1130 if ( $talkable ) {
1131 $items[] = self::userTalkLink( $userId, $userText );
1132 }
1133 if ( $userId ) {
1134 // check if the user has an edit
1135 $attribs = [];
1136 $attribs['class'] = 'mw-usertoollinks-contribs';
1137 if ( $redContribsWhenNoEdits ) {
1138 if ( intval( $edits ) === 0 && $edits !== 0 ) {
1139 $user = User::newFromId( $userId );
1140 $edits = $user->getEditCount();
1141 }
1142 if ( $edits === 0 ) {
1143 $attribs['class'] .= ' new';
1144 }
1145 }
1146 $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText );
1147
1148 $items[] = self::link( $contribsPage, wfMessage( 'contribslink' )->escaped(), $attribs );
1149 }
1150 $userCanBlock = RequestContext::getMain()->getAuthority()->isAllowed( 'block' );
1151 if ( $blockable && $userCanBlock ) {
1152 $items[] = self::blockLink( $userId, $userText );
1153 }
1154
1155 $user = RequestContext::getMain()->getUser();
1156 if ( $addEmailLink && $user->canSendEmail() ) {
1157 $items[] = self::emailLink( $userId, $userText );
1158 }
1159
1160 Hooks::runner()->onUserToolLinksEdit( $userId, $userText, $items );
1161
1162 if ( !$items ) {
1163 return '';
1164 }
1165
1166 if ( $useParentheses ) {
1167 return wfMessage( 'word-separator' )->escaped()
1168 . '<span class="mw-usertoollinks">'
1169 . wfMessage( 'parentheses' )->rawParams( $wgLang->pipeList( $items ) )->escaped()
1170 . '</span>';
1171 }
1172
1173 $tools = [];
1174 foreach ( $items as $tool ) {
1175 $tools[] = Html::rawElement( 'span', [], $tool );
1176 }
1177 return ' <span class="mw-usertoollinks mw-changeslist-links">' .
1178 implode( ' ', $tools ) . '</span>';
1179 }
1180
1190 public static function userToolLinksRedContribs(
1191 $userId, $userText, $edits = null, $useParentheses = true
1192 ) {
1193 return self::userToolLinks( $userId, $userText, true, 0, $edits, $useParentheses );
1194 }
1195
1202 public static function userTalkLink( $userId, $userText ) {
1203 if ( $userText === '' ) {
1204 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1205 'that need to be fixed?' );
1206 return wfMessage( 'empty-username' )->parse();
1207 }
1208
1209 $userTalkPage = TitleValue::tryNew( NS_USER_TALK, strtr( $userText, ' ', '_' ) );
1210 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-talk' ];
1211 $linkText = wfMessage( 'talkpagelinktext' )->escaped();
1212
1213 return $userTalkPage
1214 ? self::link( $userTalkPage, $linkText, $moreLinkAttribs )
1215 : Html::rawElement( 'span', $moreLinkAttribs, $linkText );
1216 }
1217
1224 public static function blockLink( $userId, $userText ) {
1225 if ( $userText === '' ) {
1226 wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1227 'that need to be fixed?' );
1228 return wfMessage( 'empty-username' )->parse();
1229 }
1230
1231 $blockPage = SpecialPage::getTitleFor( 'Block', $userText );
1232 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-block' ];
1233
1234 return self::link( $blockPage,
1235 wfMessage( 'blocklink' )->escaped(),
1236 $moreLinkAttribs
1237 );
1238 }
1239
1245 public static function emailLink( $userId, $userText ) {
1246 if ( $userText === '' ) {
1247 wfLogWarning( __METHOD__ . ' received an empty username. Are there database errors ' .
1248 'that need to be fixed?' );
1249 return wfMessage( 'empty-username' )->parse();
1250 }
1251
1252 $emailPage = SpecialPage::getTitleFor( 'Emailuser', $userText );
1253 $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-mail' ];
1254 return self::link( $emailPage,
1255 wfMessage( 'emaillink' )->escaped(),
1256 $moreLinkAttribs
1257 );
1258 }
1259
1271 public static function revUserLink( RevisionRecord $revRecord, $isPublic = false ) {
1272 // TODO inject authority
1273 $authority = RequestContext::getMain()->getAuthority();
1274
1275 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) && $isPublic ) {
1276 $link = wfMessage( 'rev-deleted-user' )->escaped();
1277 } elseif ( $revRecord->userCan( RevisionRecord::DELETED_USER, $authority ) ) {
1278 $revUser = $revRecord->getUser( RevisionRecord::FOR_THIS_USER, $authority );
1279 $link = self::userLink(
1280 $revUser ? $revUser->getId() : 0,
1281 $revUser ? $revUser->getName() : ''
1282 );
1283 } else {
1284 $link = wfMessage( 'rev-deleted-user' )->escaped();
1285 }
1286 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
1287 $class = self::getRevisionDeletedClass( $revRecord );
1288 return '<span class="' . $class . '">' . $link . '</span>';
1289 }
1290 return $link;
1291 }
1292
1299 public static function getRevisionDeletedClass( RevisionRecord $revisionRecord ): string {
1300 $class = 'history-deleted';
1301 if ( $revisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
1302 $class .= ' mw-history-suppressed';
1303 }
1304 return $class;
1305 }
1306
1319 public static function revUserTools(
1320 RevisionRecord $revRecord,
1321 $isPublic = false,
1322 $useParentheses = true
1323 ) {
1324 // TODO inject authority
1325 $authority = RequestContext::getMain()->getAuthority();
1326
1327 if ( $revRecord->userCan( RevisionRecord::DELETED_USER, $authority ) &&
1328 ( !$revRecord->isDeleted( RevisionRecord::DELETED_USER ) || !$isPublic )
1329 ) {
1330 $revUser = $revRecord->getUser( RevisionRecord::FOR_THIS_USER, $authority );
1331 $userId = $revUser ? $revUser->getId() : 0;
1332 $userText = $revUser ? $revUser->getName() : '';
1333
1334 if ( $userId || $userText !== '' ) {
1335 $link = self::userLink( $userId, $userText )
1336 . self::userToolLinks( $userId, $userText, false, 0, null,
1337 $useParentheses );
1338 }
1339 }
1340
1341 if ( !isset( $link ) ) {
1342 $link = wfMessage( 'rev-deleted-user' )->escaped();
1343 }
1344
1345 if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
1346 $class = self::getRevisionDeletedClass( $revRecord );
1347 return ' <span class="' . $class . ' mw-userlink">' . $link . '</span>';
1348 }
1349 return $link;
1350 }
1351
1372 public static function formatComment(
1373 $comment, $title = null, $local = false, $wikiId = null
1374 ) {
1375 # Sanitize text a bit:
1376 $comment = str_replace( "\n", " ", $comment );
1377 # Allow HTML entities (for T15815)
1378 $comment = Sanitizer::escapeHtmlAllowEntities( $comment );
1379
1380 # Render autocomments and make links:
1381 $comment = self::formatAutocomments( $comment, $title, $local, $wikiId );
1382 return self::formatLinksInComment( $comment, $title, $local, $wikiId );
1383 }
1384
1402 private static function formatAutocomments(
1403 $comment, $title = null, $local = false, $wikiId = null
1404 ) {
1405 // @todo $append here is something of a hack to preserve the status
1406 // quo. Someone who knows more about bidi and such should decide
1407 // (1) what sane rendering even *is* for an LTR edit summary on an RTL
1408 // wiki, both when autocomments exist and when they don't, and
1409 // (2) what markup will make that actually happen.
1410 $append = '';
1411 $comment = preg_replace_callback(
1412 // To detect the presence of content before or after the
1413 // auto-comment, we use capturing groups inside optional zero-width
1414 // assertions. But older versions of PCRE can't directly make
1415 // zero-width assertions optional, so wrap them in a non-capturing
1416 // group.
1417 '!(?:(?<=(.)))?/\*\s*(.*?)\s*\*/(?:(?=(.)))?!',
1418 static function ( $match ) use ( $title, $local, $wikiId, &$append ) {
1419 global $wgLang;
1420
1421 // Ensure all match positions are defined
1422 $match += [ '', '', '', '' ];
1423
1424 $pre = $match[1] !== '';
1425 $auto = $match[2];
1426 $post = $match[3] !== '';
1427 $comment = null;
1428
1429 Hooks::runner()->onFormatAutocomments(
1430 $comment, $pre, $auto, $post, Title::castFromLinkTarget( $title ), $local,
1431 $wikiId );
1432
1433 if ( $comment === null ) {
1434 if ( $title ) {
1435 $section = $auto;
1436 # Remove links that a user may have manually put in the autosummary
1437 # This could be improved by copying as much of Parser::stripSectionName as desired.
1438 $section = str_replace( [
1439 '[[:',
1440 '[[',
1441 ']]'
1442 ], '', $section );
1443
1444 // We don't want any links in the auto text to be linked, but we still
1445 // want to show any [[ ]]
1446 $sectionText = str_replace( '[[', '&#91;[', $auto );
1447
1448 $section = substr( Parser::guessSectionNameFromStrippedText( $section ), 1 );
1449 if ( $section !== '' ) {
1450 if ( $local ) {
1451 $sectionTitle = new TitleValue( NS_MAIN, '', $section );
1452 } else {
1453 $sectionTitle = $title->createFragmentTarget( $section );
1454 }
1456 $sectionTitle,
1457 $wgLang->getArrow() . $wgLang->getDirMark() . $sectionText,
1458 $wikiId,
1459 'noclasses'
1460 );
1461 }
1462 }
1463 if ( $pre ) {
1464 # written summary $presep autocomment (summary /* section */)
1465 $pre = wfMessage( 'autocomment-prefix' )->inContentLanguage()->escaped();
1466 }
1467 if ( $post ) {
1468 # autocomment $postsep written summary (/* section */ summary)
1469 $auto .= wfMessage( 'colon-separator' )->inContentLanguage()->escaped();
1470 }
1471 if ( $auto ) {
1472 $auto = '<span dir="auto"><span class="autocomment">' . $auto . '</span>';
1473 $append .= '</span>';
1474 }
1475 $comment = $pre . $auto;
1476 }
1477 return $comment;
1478 },
1479 $comment
1480 );
1481 return $comment . $append;
1482 }
1483
1503 public static function formatLinksInComment(
1504 $comment, $title = null, $local = false, $wikiId = null
1505 ) {
1506 return preg_replace_callback(
1507 '/
1508 \[\[
1509 \s*+ # ignore leading whitespace, the *+ quantifier disallows backtracking
1510 :? # ignore optional leading colon
1511 ([^[\]|]+) # 1. link target; page names cannot include [, ] or |
1512 (?:\|
1513 # 2. link text
1514 # Stop matching at ]] without relying on backtracking.
1515 ((?:]?[^\]])*+)
1516 )?
1517 \]\]
1518 ([^[]*) # 3. link trail (the text up until the next link)
1519 /x',
1520 static function ( $match ) use ( $title, $local, $wikiId ) {
1521 $services = MediaWikiServices::getInstance();
1522
1523 $medians = '(?:';
1524 $medians .= preg_quote(
1525 $services->getNamespaceInfo()->getCanonicalName( NS_MEDIA ), '/' );
1526 $medians .= '|';
1527 $medians .= preg_quote(
1528 $services->getContentLanguage()->getNsText( NS_MEDIA ),
1529 '/'
1530 ) . '):';
1531
1532 $comment = $match[0];
1533
1534 # fix up urlencoded title texts (copied from Parser::replaceInternalLinks)
1535 if ( strpos( $match[1], '%' ) !== false ) {
1536 $match[1] = strtr(
1537 rawurldecode( $match[1] ),
1538 [ '<' => '&lt;', '>' => '&gt;' ]
1539 );
1540 }
1541
1542 # Handle link renaming [[foo|text]] will show link as "text"
1543 if ( $match[2] != "" ) {
1544 $text = $match[2];
1545 } else {
1546 $text = $match[1];
1547 }
1548 $submatch = [];
1549 $thelink = null;
1550 if ( preg_match( '/^' . $medians . '(.*)$/i', $match[1], $submatch ) ) {
1551 # Media link; trail not supported.
1552 $linkRegexp = '/\[\[(.*?)\]\]/';
1553 $title = Title::makeTitleSafe( NS_FILE, $submatch[1] );
1554 if ( $title ) {
1555 $thelink = Linker::makeMediaLinkObj( $title, $text );
1556 }
1557 } else {
1558 # Other kind of link
1559 # Make sure its target is non-empty
1560 if ( isset( $match[1][0] ) && $match[1][0] == ':' ) {
1561 $match[1] = substr( $match[1], 1 );
1562 }
1563 if ( $match[1] !== false && $match[1] !== '' ) {
1564 if ( preg_match(
1565 $services->getContentLanguage()->linkTrail(),
1566 $match[3],
1567 $submatch
1568 ) ) {
1569 $trail = $submatch[1];
1570 } else {
1571 $trail = "";
1572 }
1573 $linkRegexp = '/\[\[(.*?)\]\]' . preg_quote( $trail, '/' ) . '/';
1574 list( $inside, $trail ) = Linker::splitTrail( $trail );
1575
1576 $linkText = $text;
1577 $linkTarget = Linker::normalizeSubpageLink( $title, $match[1], $linkText );
1578
1579 try {
1580 $target = $services->getTitleParser()->
1581 parseTitle( $linkTarget );
1582
1583 if ( $target->getText() == '' && !$target->isExternal()
1584 && !$local && $title
1585 ) {
1586 $target = $title->createFragmentTarget( $target->getFragment() );
1587 }
1588
1589 $thelink = Linker::makeCommentLink( $target, $linkText . $inside, $wikiId ) . $trail;
1590 } catch ( MalformedTitleException $e ) {
1591 // Fall through
1592 }
1593 }
1594 }
1595 if ( $thelink ) {
1596 // If the link is still valid, go ahead and replace it in!
1597 $comment = preg_replace(
1598 $linkRegexp,
1600 $comment,
1601 1
1602 );
1603 }
1604
1605 return $comment;
1606 },
1607 $comment
1608 );
1609 }
1610
1624 public static function makeCommentLink(
1625 LinkTarget $linkTarget, $text, $wikiId = null, $options = []
1626 ) {
1627 if ( $wikiId !== null && !$linkTarget->isExternal() ) {
1628 $link = self::makeExternalLink(
1629 WikiMap::getForeignURL(
1630 $wikiId,
1631 $linkTarget->getNamespace() === 0
1632 ? $linkTarget->getDBkey()
1633 : MediaWikiServices::getInstance()->getNamespaceInfo()->
1634 getCanonicalName( $linkTarget->getNamespace() ) .
1635 ':' . $linkTarget->getDBkey(),
1636 $linkTarget->getFragment()
1637 ),
1638 $text,
1639 /* escape = */ false // Already escaped
1640 );
1641 } else {
1642 $link = self::link( $linkTarget, $text, [], [], $options );
1643 }
1644
1645 return $link;
1646 }
1647
1654 public static function normalizeSubpageLink( $contextTitle, $target, &$text ) {
1655 # Valid link forms:
1656 # Foobar -- normal
1657 # :Foobar -- override special treatment of prefix (images, language links)
1658 # /Foobar -- convert to CurrentPage/Foobar
1659 # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial and final / from text
1660 # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage
1661 # ../Foobar -- convert to CurrentPage/Foobar,
1662 # (from CurrentPage/CurrentSubPage)
1663 # ../Foobar/ -- convert to CurrentPage/Foobar, use 'Foobar' as text
1664 # (from CurrentPage/CurrentSubPage)
1665
1666 $ret = $target; # default return value is no change
1667
1668 # Some namespaces don't allow subpages,
1669 # so only perform processing if subpages are allowed
1670 if (
1671 $contextTitle && MediaWikiServices::getInstance()->getNamespaceInfo()->
1672 hasSubpages( $contextTitle->getNamespace() )
1673 ) {
1674 $hash = strpos( $target, '#' );
1675 if ( $hash !== false ) {
1676 $suffix = substr( $target, $hash );
1677 $target = substr( $target, 0, $hash );
1678 } else {
1679 $suffix = '';
1680 }
1681 # T9425
1682 $target = trim( $target );
1683 $contextPrefixedText = MediaWikiServices::getInstance()->getTitleFormatter()->
1684 getPrefixedText( $contextTitle );
1685 # Look at the first character
1686 if ( $target != '' && $target[0] === '/' ) {
1687 # / at end means we don't want the slash to be shown
1688 $m = [];
1689 $trailingSlashes = preg_match_all( '%(/+)$%', $target, $m );
1690 if ( $trailingSlashes ) {
1691 $noslash = $target = substr( $target, 1, -strlen( $m[0][0] ) );
1692 } else {
1693 $noslash = substr( $target, 1 );
1694 }
1695
1696 $ret = $contextPrefixedText . '/' . trim( $noslash ) . $suffix;
1697 if ( $text === '' ) {
1698 $text = $target . $suffix;
1699 } # this might be changed for ugliness reasons
1700 } else {
1701 # check for .. subpage backlinks
1702 $dotdotcount = 0;
1703 $nodotdot = $target;
1704 while ( strncmp( $nodotdot, "../", 3 ) == 0 ) {
1705 ++$dotdotcount;
1706 $nodotdot = substr( $nodotdot, 3 );
1707 }
1708 if ( $dotdotcount > 0 ) {
1709 $exploded = explode( '/', $contextPrefixedText );
1710 if ( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page
1711 $ret = implode( '/', array_slice( $exploded, 0, -$dotdotcount ) );
1712 # / at the end means don't show full path
1713 if ( substr( $nodotdot, -1, 1 ) === '/' ) {
1714 $nodotdot = rtrim( $nodotdot, '/' );
1715 if ( $text === '' ) {
1716 $text = $nodotdot . $suffix;
1717 }
1718 }
1719 $nodotdot = trim( $nodotdot );
1720 if ( $nodotdot != '' ) {
1721 $ret .= '/' . $nodotdot;
1722 }
1723 $ret .= $suffix;
1724 }
1725 }
1726 }
1727 }
1728
1729 return $ret;
1730 }
1731
1749 public static function commentBlock(
1750 $comment, $title = null, $local = false, $wikiId = null, $useParentheses = true
1751 ) {
1752 // '*' used to be the comment inserted by the software way back
1753 // in antiquity in case none was provided, here for backwards
1754 // compatibility, acc. to brion -ævar
1755 if ( $comment == '' || $comment == '*' ) {
1756 return '';
1757 }
1758 $formatted = self::formatComment( $comment, $title, $local, $wikiId );
1759 if ( $useParentheses ) {
1760 $formatted = wfMessage( 'parentheses' )->rawParams( $formatted )->escaped();
1761 $classNames = 'comment';
1762 } else {
1763 $classNames = 'comment comment--without-parentheses';
1764 }
1765 return " <span class=\"$classNames\">$formatted</span>";
1766 }
1767
1782 public static function revComment(
1783 RevisionRecord $revRecord,
1784 $local = false,
1785 $isPublic = false,
1786 $useParentheses = true
1787 ) {
1788 // TODO inject authority
1789 $authority = RequestContext::getMain()->getAuthority();
1790
1791 if ( $revRecord->getComment( RevisionRecord::RAW ) === null ) {
1792 return "";
1793 }
1794 if ( $revRecord->isDeleted( RevisionRecord::DELETED_COMMENT ) && $isPublic ) {
1795 $block = " <span class=\"comment\">" . wfMessage( 'rev-deleted-comment' )->escaped() . "</span>";
1796 } elseif ( $revRecord->userCan( RevisionRecord::DELETED_COMMENT, $authority ) ) {
1797 $comment = $revRecord->getComment( RevisionRecord::FOR_THIS_USER, $authority );
1798 $block = self::commentBlock(
1799 $comment ? $comment->text : null,
1800 $revRecord->getPageAsLinkTarget(),
1801 $local,
1802 null,
1803 $useParentheses
1804 );
1805 } else {
1806 $block = " <span class=\"comment\">" . wfMessage( 'rev-deleted-comment' )->escaped() . "</span>";
1807 }
1808 if ( $revRecord->isDeleted( RevisionRecord::DELETED_COMMENT ) ) {
1809 $class = self::getRevisionDeletedClass( $revRecord );
1810 return " <span class=\"$class comment\">$block</span>";
1811 }
1812 return $block;
1813 }
1814
1820 public static function formatRevisionSize( $size ) {
1821 if ( $size == 0 ) {
1822 $stxt = wfMessage( 'historyempty' )->escaped();
1823 } else {
1824 $stxt = wfMessage( 'nbytes' )->numParams( $size )->escaped();
1825 }
1826 return "<span class=\"history-size mw-diff-bytes\" data-mw-bytes=\"$size\">$stxt</span>";
1827 }
1828
1835 public static function tocIndent() {
1836 return "\n<ul>\n";
1837 }
1838
1846 public static function tocUnindent( $level ) {
1847 return "</li>\n" . str_repeat( "</ul>\n</li>\n", $level > 0 ? $level : 0 );
1848 }
1849
1861 public static function tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex = false ) {
1862 $classes = "toclevel-$level";
1863 if ( $sectionIndex !== false ) {
1864 $classes .= " tocsection-$sectionIndex";
1865 }
1866
1867 // <li class="$classes"><a href="#$anchor"><span class="tocnumber">
1868 // $tocnumber</span> <span class="toctext">$tocline</span></a>
1869 return Html::openElement( 'li', [ 'class' => $classes ] )
1870 . Html::rawElement( 'a',
1871 [ 'href' => "#$anchor" ],
1872 Html::element( 'span', [ 'class' => 'tocnumber' ], $tocnumber )
1873 . ' '
1874 . Html::rawElement( 'span', [ 'class' => 'toctext' ], $tocline )
1875 );
1876 }
1877
1885 public static function tocLineEnd() {
1886 return "</li>\n";
1887 }
1888
1897 public static function tocList( $toc, Language $lang = null ) {
1898 $lang = $lang ?? RequestContext::getMain()->getLanguage();
1899
1900 $title = wfMessage( 'toc' )->inLanguage( $lang )->escaped();
1901
1902 return '<div id="toc" class="toc" role="navigation" aria-labelledby="mw-toc-heading">'
1903 . Html::element( 'input', [
1904 'type' => 'checkbox',
1905 'role' => 'button',
1906 'id' => 'toctogglecheckbox',
1907 'class' => 'toctogglecheckbox',
1908 'style' => 'display:none',
1909 ] )
1910 . Html::openElement( 'div', [
1911 'class' => 'toctitle',
1912 'lang' => $lang->getHtmlCode(),
1913 'dir' => $lang->getDir(),
1914 ] )
1915 . '<h2 id="mw-toc-heading">' . $title . '</h2>'
1916 . '<span class="toctogglespan">'
1917 . Html::label( '', 'toctogglecheckbox', [
1918 'class' => 'toctogglelabel',
1919 ] )
1920 . '</span>'
1921 . "</div>\n"
1922 . $toc
1923 . "</ul>\n</div>\n";
1924 }
1925
1934 public static function generateTOC( $tree, Language $lang = null ) {
1935 $toc = '';
1936 $lastLevel = 0;
1937 foreach ( $tree as $section ) {
1938 if ( $section['toclevel'] > $lastLevel ) {
1939 $toc .= self::tocIndent();
1940 } elseif ( $section['toclevel'] < $lastLevel ) {
1941 $toc .= self::tocUnindent(
1942 $lastLevel - $section['toclevel'] );
1943 } else {
1944 $toc .= self::tocLineEnd();
1945 }
1946
1947 $toc .= self::tocLine( $section['anchor'],
1948 $section['line'], $section['number'],
1949 $section['toclevel'], $section['index'] );
1950 $lastLevel = $section['toclevel'];
1951 }
1952 $toc .= self::tocLineEnd();
1953 return self::tocList( $toc, $lang );
1954 }
1955
1972 public static function makeHeadline( $level, $attribs, $anchor, $html,
1973 $link, $fallbackAnchor = false
1974 ) {
1975 $anchorEscaped = htmlspecialchars( $anchor );
1976 $fallback = '';
1977 if ( $fallbackAnchor !== false && $fallbackAnchor !== $anchor ) {
1978 $fallbackAnchor = htmlspecialchars( $fallbackAnchor );
1979 $fallback = "<span id=\"$fallbackAnchor\"></span>";
1980 }
1981 return "<h$level$attribs"
1982 . "$fallback<span class=\"mw-headline\" id=\"$anchorEscaped\">$html</span>"
1983 . $link
1984 . "</h$level>";
1985 }
1986
1993 public static function splitTrail( $trail ) {
1994 $regex = MediaWikiServices::getInstance()->getContentLanguage()->linkTrail();
1995 $inside = '';
1996 if ( $trail !== '' && preg_match( $regex, $trail, $m ) ) {
1997 list( , $inside, $trail ) = $m;
1998 }
1999 return [ $inside, $trail ];
2000 }
2001
2031 public static function generateRollback(
2032 RevisionRecord $revRecord,
2033 IContextSource $context = null,
2034 $options = [ 'verify' ]
2035 ) {
2036 if ( $context === null ) {
2037 $context = RequestContext::getMain();
2038 }
2039
2040 $editCount = false;
2041 if ( in_array( 'verify', $options, true ) ) {
2042 $editCount = self::getRollbackEditCount( $revRecord, true );
2043 if ( $editCount === false ) {
2044 return '';
2045 }
2046 }
2047
2048 $inner = self::buildRollbackLink( $revRecord, $context, $editCount );
2049
2050 // Allow extensions to modify the rollback link.
2051 // Abort further execution if the extension wants full control over the link.
2052 if ( !Hooks::runner()->onLinkerGenerateRollbackLink(
2053 $revRecord, $context, $options, $inner ) ) {
2054 return $inner;
2055 }
2056
2057 if ( !in_array( 'noBrackets', $options, true ) ) {
2058 $inner = $context->msg( 'brackets' )->rawParams( $inner )->escaped();
2059 }
2060
2061 if ( MediaWikiServices::getInstance()->getUserOptionsLookup()
2062 ->getBoolOption( $context->getUser(), 'showrollbackconfirmation' )
2063 ) {
2064 $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
2065 $stats->increment( 'rollbackconfirmation.event.load' );
2066 $context->getOutput()->addModules( 'mediawiki.misc-authed-curate' );
2067 }
2068
2069 return '<span class="mw-rollback-link">' . $inner . '</span>';
2070 }
2071
2090 public static function getRollbackEditCount( RevisionRecord $revRecord, $verify ) {
2092
2093 if ( !is_int( $wgShowRollbackEditCount ) || !$wgShowRollbackEditCount > 0 ) {
2094 // Nothing has happened, indicate this by returning 'null'
2095 return null;
2096 }
2097
2098 $dbr = wfGetDB( DB_REPLICA );
2099
2100 // Up to the value of $wgShowRollbackEditCount revisions are counted
2101 $revQuery = MediaWikiServices::getInstance()->getRevisionStore()->getQueryInfo();
2102 // T270033 Index renaming
2103 $revIndex = $dbr->indexExists( 'revision', 'page_timestamp', __METHOD__ )
2104 ? 'page_timestamp'
2105 : 'rev_page_timestamp';
2106 $res = $dbr->select(
2107 $revQuery['tables'],
2108 [ 'rev_user_text' => $revQuery['fields']['rev_user_text'], 'rev_deleted' ],
2109 [ 'rev_page' => $revRecord->getPageId() ],
2110 __METHOD__,
2111 [
2112 'USE INDEX' => [ 'revision' => $revIndex ],
2113 'ORDER BY' => 'rev_timestamp DESC',
2114 'LIMIT' => $wgShowRollbackEditCount + 1
2115 ],
2116 $revQuery['joins']
2117 );
2118
2119 $revUser = $revRecord->getUser( RevisionRecord::RAW );
2120 $revUserText = $revUser ? $revUser->getName() : '';
2121
2122 $editCount = 0;
2123 $moreRevs = false;
2124 foreach ( $res as $row ) {
2125 if ( $row->rev_user_text != $revUserText ) {
2126 if ( $verify &&
2127 ( $row->rev_deleted & RevisionRecord::DELETED_TEXT
2128 || $row->rev_deleted & RevisionRecord::DELETED_USER
2129 ) ) {
2130 // If the user or the text of the revision we might rollback
2131 // to is deleted in some way we can't rollback. Similar to
2132 // the sanity checks in WikiPage::commitRollback.
2133 return false;
2134 }
2135 $moreRevs = true;
2136 break;
2137 }
2138 $editCount++;
2139 }
2140
2141 if ( $verify && $editCount <= $wgShowRollbackEditCount && !$moreRevs ) {
2142 // We didn't find at least $wgShowRollbackEditCount revisions made by the current user
2143 // and there weren't any other revisions. That means that the current user is the only
2144 // editor, so we can't rollback
2145 return false;
2146 }
2147 return $editCount;
2148 }
2149
2164 public static function buildRollbackLink(
2165 RevisionRecord $revRecord,
2166 IContextSource $context = null,
2167 $editCount = false
2168 ) {
2170
2171 // To config which pages are affected by miser mode
2172 $disableRollbackEditCountSpecialPage = [ 'Recentchanges', 'Watchlist' ];
2173
2174 if ( $context === null ) {
2175 $context = RequestContext::getMain();
2176 }
2177
2178 $title = $revRecord->getPageAsLinkTarget();
2179 $revUser = $revRecord->getUser();
2180 $revUserText = $revUser ? $revUser->getName() : '';
2181
2182 $query = [
2183 'action' => 'rollback',
2184 'from' => $revUserText,
2185 'token' => $context->getUser()->getEditToken( 'rollback' ),
2186 ];
2187
2188 $attrs = [
2189 'data-mw' => 'interface',
2190 'title' => $context->msg( 'tooltip-rollback' )->text()
2191 ];
2192
2193 $options = [ 'known', 'noclasses' ];
2194
2195 if ( $context->getRequest()->getBool( 'bot' ) ) {
2196 // T17999
2197 $query['hidediff'] = '1';
2198 $query['bot'] = '1';
2199 }
2200
2201 $disableRollbackEditCount = false;
2202 if ( $wgMiserMode ) {
2203 foreach ( $disableRollbackEditCountSpecialPage as $specialPage ) {
2204 if ( $context->getTitle()->isSpecial( $specialPage ) ) {
2205 $disableRollbackEditCount = true;
2206 break;
2207 }
2208 }
2209 }
2210
2211 if ( !$disableRollbackEditCount
2212 && is_int( $wgShowRollbackEditCount )
2214 ) {
2215 if ( !is_numeric( $editCount ) ) {
2216 $editCount = self::getRollbackEditCount( $revRecord, false );
2217 }
2218
2219 if ( $editCount > $wgShowRollbackEditCount ) {
2220 $html = $context->msg( 'rollbacklinkcount-morethan' )
2221 ->numParams( $wgShowRollbackEditCount )->parse();
2222 } else {
2223 $html = $context->msg( 'rollbacklinkcount' )->numParams( $editCount )->parse();
2224 }
2225
2226 return self::link( $title, $html, $attrs, $query, $options );
2227 }
2228
2229 $html = $context->msg( 'rollbacklink' )->escaped();
2230 return self::link( $title, $html, $attrs, $query, $options );
2231 }
2232
2241 public static function formatHiddenCategories( $hiddencats ) {
2242 $outText = '';
2243 if ( count( $hiddencats ) > 0 ) {
2244 # Construct the HTML
2245 $outText = '<div class="mw-hiddenCategoriesExplanation">';
2246 $outText .= wfMessage( 'hiddencategories' )->numParams( count( $hiddencats ) )->parseAsBlock();
2247 $outText .= "</div><ul>\n";
2248
2249 foreach ( $hiddencats as $titleObj ) {
2250 # If it's hidden, it must exist - no need to check with a LinkBatch
2251 $outText .= '<li>'
2252 . self::link( $titleObj, null, [], [], 'known' )
2253 . "</li>\n";
2254 }
2255 $outText .= '</ul>';
2256 }
2257 return $outText;
2258 }
2259
2276 public static function titleAttrib( $name, $options = null, array $msgParams = [] ) {
2277 $message = wfMessage( "tooltip-$name", $msgParams );
2278 if ( !$message->exists() ) {
2279 $tooltip = false;
2280 } else {
2281 $tooltip = $message->text();
2282 # Compatibility: formerly some tooltips had [alt-.] hardcoded
2283 $tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip );
2284 # Message equal to '-' means suppress it.
2285 if ( $tooltip == '-' ) {
2286 $tooltip = false;
2287 }
2288 }
2289
2290 $options = (array)$options;
2291
2292 if ( in_array( 'nonexisting', $options ) ) {
2293 $tooltip = wfMessage( 'red-link-title', $tooltip ?: '' )->text();
2294 }
2295 if ( in_array( 'withaccess', $options ) ) {
2296 $accesskey = self::accesskey( $name );
2297 if ( $accesskey !== false ) {
2298 // Should be build the same as in jquery.accessKeyLabel.js
2299 if ( $tooltip === false || $tooltip === '' ) {
2300 $tooltip = wfMessage( 'brackets', $accesskey )->text();
2301 } else {
2302 $tooltip .= wfMessage( 'word-separator' )->text();
2303 $tooltip .= wfMessage( 'brackets', $accesskey )->text();
2304 }
2305 }
2306 }
2307
2308 return $tooltip;
2309 }
2310
2311 public static $accesskeycache;
2312
2324 public static function accesskey( $name ) {
2325 if ( isset( self::$accesskeycache[$name] ) ) {
2326 return self::$accesskeycache[$name];
2327 }
2328
2329 $message = wfMessage( "accesskey-$name" );
2330
2331 if ( !$message->exists() ) {
2332 $accesskey = false;
2333 } else {
2334 $accesskey = $message->plain();
2335 if ( $accesskey === '' || $accesskey === '-' ) {
2336 # Per standard MW behavior, a value of '-' means to suppress the
2337 # attribute. It is thus forbidden to use this as an access key.
2338 $accesskey = false;
2339 }
2340 }
2341
2342 self::$accesskeycache[$name] = $accesskey;
2343 return self::$accesskeycache[$name];
2344 }
2345
2360 public static function getRevDeleteLink(
2361 Authority $performer,
2362 RevisionRecord $revRecord,
2364 ) {
2365 $canHide = $performer->isAllowed( 'deleterevision' );
2366 $canHideHistory = $performer->isAllowed( 'deletedhistory' );
2367 if ( !$canHide && !( $revRecord->getVisibility() && $canHideHistory ) ) {
2368 return '';
2369 }
2370
2371 if ( !$revRecord->userCan( RevisionRecord::DELETED_RESTRICTED, $performer ) ) {
2372 return self::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
2373 }
2374 $prefixedDbKey = MediaWikiServices::getInstance()->getTitleFormatter()->
2375 getPrefixedDBkey( $title );
2376 if ( $revRecord->getId() ) {
2377 // RevDelete links using revision ID are stable across
2378 // page deletion and undeletion; use when possible.
2379 $query = [
2380 'type' => 'revision',
2381 'target' => $prefixedDbKey,
2382 'ids' => $revRecord->getId()
2383 ];
2384 } else {
2385 // Older deleted entries didn't save a revision ID.
2386 // We have to refer to these by timestamp, ick!
2387 $query = [
2388 'type' => 'archive',
2389 'target' => $prefixedDbKey,
2390 'ids' => $revRecord->getTimestamp()
2391 ];
2392 }
2393 return self::revDeleteLink(
2394 $query,
2395 $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ),
2396 $canHide
2397 );
2398 }
2399
2412 public static function revDeleteLink( $query = [], $restricted = false, $delete = true ) {
2413 $sp = SpecialPage::getTitleFor( 'Revisiondelete' );
2414 $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
2415 $html = wfMessage( $msgKey )->escaped();
2416 $tag = $restricted ? 'strong' : 'span';
2417 $link = self::link( $sp, $html, [], $query, [ 'known', 'noclasses' ] );
2418 return Xml::tags(
2419 $tag,
2420 [ 'class' => 'mw-revdelundel-link' ],
2421 wfMessage( 'parentheses' )->rawParams( $link )->escaped()
2422 );
2423 }
2424
2436 public static function revDeleteLinkDisabled( $delete = true ) {
2437 $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
2438 $html = wfMessage( $msgKey )->escaped();
2439 $htmlParentheses = wfMessage( 'parentheses' )->rawParams( $html )->escaped();
2440 return Xml::tags( 'span', [ 'class' => 'mw-revdelundel-link' ], $htmlParentheses );
2441 }
2442
2455 public static function tooltipAndAccesskeyAttribs(
2456 $name,
2457 array $msgParams = [],
2458 $options = null
2459 ) {
2460 $options = (array)$options;
2461 $options[] = 'withaccess';
2462 $tooltipTitle = $name;
2463
2464 // @since 1.35 - add a WatchlistExpiry feature flag to show new watchstar tooltip message
2465 $skin = RequestContext::getMain()->getSkin();
2466 $isWatchlistExpiryEnabled = $skin->getConfig()->get( 'WatchlistExpiry' );
2467 if ( $name === 'ca-unwatch' && $isWatchlistExpiryEnabled ) {
2468 $watchStore = MediaWikiServices::getInstance()->getWatchedItemStore();
2469 $watchedItem = $watchStore->getWatchedItem( $skin->getUser(),
2470 $skin->getRelevantTitle() );
2471 if ( $watchedItem instanceof WatchedItem && $watchedItem->getExpiry() !== null ) {
2472 $diffInDays = $watchedItem->getExpiryInDays();
2473
2474 if ( $diffInDays ) {
2475 $msgParams = [ $diffInDays ];
2476 // Resolves to tooltip-ca-unwatch-expiring message
2477 $tooltipTitle = 'ca-unwatch-expiring';
2478 } else { // Resolves to tooltip-ca-unwatch-expiring-hours message
2479 $tooltipTitle = 'ca-unwatch-expiring-hours';
2480 }
2481
2482 }
2483 }
2484
2485 $attribs = [
2486 'title' => self::titleAttrib( $tooltipTitle, $options, $msgParams ),
2487 'accesskey' => self::accesskey( $name )
2488 ];
2489 if ( $attribs['title'] === false ) {
2490 unset( $attribs['title'] );
2491 }
2492 if ( $attribs['accesskey'] === false ) {
2493 unset( $attribs['accesskey'] );
2494 }
2495 return $attribs;
2496 }
2497
2505 public static function tooltip( $name, $options = null ) {
2506 $tooltip = self::titleAttrib( $name, $options );
2507 if ( $tooltip === false ) {
2508 return '';
2509 }
2510 return Xml::expandAttributes( [
2511 'title' => $tooltip
2512 ] );
2513 }
2514
2515}
UserOptionsLookup $userOptionsLookup
$wgThumbUpright
Adjust width of upright images when parameter 'upright' is used This allows a nicer look for upright ...
$wgThumbLimits
Adjust thumbnails on image pages according to a user setting.
$wgDisableAnonTalk
Disable links to talk pages of anonymous users (IPs) in listings on special pages like page history,...
$wgSVGMaxSize
Don't scale a SVG larger than this.
$wgShowRollbackEditCount
The $wgShowRollbackEditCount variable is used to show how many edits can be rolled back.
$wgUploadMissingFileUrl
Point the upload link for missing files to an external URL, as with $wgUploadNavigationUrl.
$wgUploadNavigationUrl
Point the upload navigation link to an external URL Useful if you want to use a shared repository by ...
$wgResponsiveImages
Generate and use thumbnails suitable for screens with 1.5 and 2.0 pixel densities.
$wgEnableUploads
Allow users to upload files.
$wgMiserMode
Disable database-intensive features.
const NS_USER
Definition Defines.php:66
const NS_FILE
Definition Defines.php:70
const NS_MAIN
Definition Defines.php:64
const NS_MEDIA
Definition Defines.php:52
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.
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
$wgLang
Definition Setup.php:831
$wgTitle
Definition Setup.php:849
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:42
Some internal bits split of from Skin.php.
Definition Linker.php:38
static makeMediaLinkFile(LinkTarget $title, $file, $html='')
Create a direct link to a given uploaded file.
Definition Linker.php:943
static link( $target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition Linker.php:87
static fnamePart( $url)
Returns the filename part of an url.
Definition Linker.php:220
static tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition Linker.php:1861
static userLink( $userId, $userName, $altUserName=false)
Make user link (or user contributions for unregistered users)
Definition Linker.php:1064
static $accesskeycache
Definition Linker.php:2311
static titleAttrib( $name, $options=null, array $msgParams=[])
Given the id of an interface element, constructs the appropriate title attribute from the system mess...
Definition Linker.php:2276
static accesskey( $name)
Given the id of an interface element, constructs the appropriate accesskey attribute from the system ...
Definition Linker.php:2324
static getRevisionDeletedClass(RevisionRecord $revisionRecord)
Returns css class of a deleted revision.
Definition Linker.php:1299
static formatAutocomments( $comment, $title=null, $local=false, $wikiId=null)
Converts autogenerated comments in edit summaries into section links.
Definition Linker.php:1402
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:985
static getRollbackEditCount(RevisionRecord $revRecord, $verify)
This function will return the number of revisions which a rollback would revert and,...
Definition Linker.php:2090
static linkKnown( $target, $html=null, $customAttribs=[], $query=[], $options=[ 'known'])
Identical to link(), except $options defaults to 'known'.
Definition Linker.php:139
static makeExternalImage( $url, $alt='')
Return the code for images which were added via external links, via Parser::maybeMakeExternalImage().
Definition Linker.php:240
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:1782
static makeCommentLink(LinkTarget $linkTarget, $text, $wikiId=null, $options=[])
Generates a link to the given LinkTarget.
Definition Linker.php:1624
static revUserLink(RevisionRecord $revRecord, $isPublic=false)
Generate a user link if the current user is allowed to view it.
Definition Linker.php:1271
static processResponsiveImages( $file, $thumb, $hp)
Process responsive images: add 1.5x and 2x subimages to the thumbnail, where applicable.
Definition Linker.php:792
static blockLink( $userId, $userText)
Definition Linker.php:1224
static revDeleteLinkDisabled( $delete=true)
Creates a dead (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2436
static normalizeSubpageLink( $contextTitle, $target, &$text)
Definition Linker.php:1654
const TOOL_LINKS_NOBLOCK
Flags for userToolLinks()
Definition Linker.php:42
static makeBrokenImageLinkObj( $title, $label='', $query='', $unused1='', $unused2='', $time=false, array $handlerParams=[])
Make a "broken" link to an image.
Definition Linker.php:828
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:2360
static makeSelfLinkObj( $nt, $html='', $query='', $trail='', $prefix='')
Make appropriate markup for a link to the current article.
Definition Linker.php:161
static getUploadUrl( $destFile, $query='')
Get the URL to upload a certain file.
Definition Linker.php:895
static tocIndent()
Add another level to the Table of Contents.
Definition Linker.php:1835
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:296
static getInvalidTitleDescription(IContextSource $context, $namespace, $title)
Get a message saying that an invalid title was encountered.
Definition Linker.php:185
static emailLink( $userId, $userText)
Definition Linker.php:1245
static generateRollback(RevisionRecord $revRecord, IContextSource $context=null, $options=[ 'verify'])
Generate a rollback link for a given revision.
Definition Linker.php:2031
static formatHiddenCategories( $hiddencats)
Returns HTML for the "hidden categories on this page" list.
Definition Linker.php:2241
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:1993
static generateTOC( $tree, Language $lang=null)
Generate a table of contents from a section tree.
Definition Linker.php:1934
const TOOL_LINKS_EMAIL
Definition Linker.php:43
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:567
static formatRevisionSize( $size)
Definition Linker.php:1820
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:1749
static tooltip( $name, $options=null)
Returns raw bits of HTML, use titleAttrib()
Definition Linker.php:2505
static userTalkLink( $userId, $userText)
Definition Linker.php:1202
static buildRollbackLink(RevisionRecord $revRecord, IContextSource $context=null, $editCount=false)
Build a raw rollback link, useful for collections of "tool" links.
Definition Linker.php:2164
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:1319
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:1503
static makeThumbLink2(LinkTarget $title, $file, $frameParams=[], $handlerParams=[], $time=false, $query="", array $classes=[])
Definition Linker.php:598
static makeMediaLinkObj( $title, $html='', $time=false)
Create a direct link to a given uploaded file.
Definition Linker.php:924
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition Linker.php:1011
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:1109
static normaliseSpecialPage(LinkTarget $target)
Definition Linker.php:206
static makeHeadline( $level, $attribs, $anchor, $html, $link, $fallbackAnchor=false)
Create a headline for content.
Definition Linker.php:1972
static tocUnindent( $level)
Finish one or more sublevels on the Table of Contents.
Definition Linker.php:1846
static tooltipAndAccesskeyAttribs( $name, array $msgParams=[], $options=null)
Returns the attributes for the tooltip and access key.
Definition Linker.php:2455
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:1897
static getImageLinkMTOParams( $frameParams, $query='', $parser=null)
Get the link parameters for MediaTransformOutput::toHtml() from given frame parameters supplied by th...
Definition Linker.php:527
static revDeleteLink( $query=[], $restricted=false, $delete=true)
Creates a (show/hide) link for deleting revisions/log entries.
Definition Linker.php:2412
static tocLineEnd()
End a Table Of Contents line.
Definition Linker.php:1885
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:1372
static userToolLinksRedContribs( $userId, $userText, $edits=null, $useParentheses=true)
Alias for userToolLinks( $userId, $userText, true );.
Definition Linker.php:1190
MalformedTitleException is thrown when a TitleParser is unable to parse a title string.
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.
getComment( $audience=self::FOR_PUBLIC, Authority $performer=null)
Fetch revision comment, if it's available to the specified audience.
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.
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition Parser.php:91
getTargetLanguage()
Get the target language for the content being parsed.
Definition Parser.php:1156
static guessSectionNameFromStrippedText( $text)
Like guessSectionNameFromWikiText(), but takes already-stripped text as input.
Definition Parser.php:6102
static getExternalLinkRel( $url=false, LinkTarget $title=null)
Get the rel attribute for a particular external link.
Definition Parser.php:2234
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 escapeRegexReplacement( $string)
Escape a string to make it suitable for inclusion in a preg_replace() replacement parameter.
Represents a page (or page fragment) title within MediaWiki.
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition User.php:648
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.
getFragment()
Get the link fragment (i.e.
getNamespace()
Get the namespace index.
getDBkey()
Get the main part with underscores.
isExternal()
Whether this LinkTarget has an interwiki component.
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