MediaWiki  master
Linker.php
Go to the documentation of this file.
1 <?php
23 use HtmlFormatter\HtmlFormatter;
28 use Wikimedia\IPUtils;
29 
39 class 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  ) {
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 .
412  self::makeThumbLink2( $title, $file, $frameParams, $handlerParams, $time, $query, $classes ) .
413  $postfix;
414  }
415 
416  switch ( $file ? $file->getMediaType() : '' ) {
417  case 'AUDIO':
418  $rdfaType = 'mw:Audio';
419  break;
420  case 'VIDEO':
421  $rdfaType = 'mw:Video';
422  break;
423  default:
424  $rdfaType = 'mw:Image';
425  }
426 
427  if ( $file && isset( $frameParams['frameless'] ) ) {
428  $rdfaType .= '/Frameless';
429  $srcWidth = $file->getWidth( $page );
430  # For "frameless" option: do not present an image bigger than the
431  # source (for bitmap-style images). This is the same behavior as the
432  # "thumb" option does it already.
433  if ( $srcWidth && !$file->mustRender() && $handlerParams['width'] > $srcWidth ) {
434  $handlerParams['width'] = $srcWidth;
435  }
436  }
437 
438  if ( $file && isset( $handlerParams['width'] ) ) {
439  # Create a resized image, without the additional thumbnail features
440  $thumb = $file->transform( $handlerParams );
441  } else {
442  $thumb = false;
443  }
444 
445  if ( !$thumb ) {
446  $rdfaType = 'mw:Error ' . $rdfaType;
447  $label = '';
448  if ( $enableLegacyMediaDOM ) {
449  // This is the information for tooltips for inline images which
450  // Parsoid stores in data-mw. See T273014
451  $label = $frameParams['title'];
452  }
454  $title, $label, '', '', '', (bool)$time, $handlerParams
455  );
456  } else {
457  self::processResponsiveImages( $file, $thumb, $handlerParams );
458  $params = [
459  'alt' => $frameParams['alt'],
460  'title' => $frameParams['title'],
461  ];
462  if ( $enableLegacyMediaDOM ) {
463  $params += [
464  'valign' => $frameParams['valign'] ?? false,
465  'img-class' => $frameParams['class'],
466  ];
467  if ( isset( $frameParams['border'] ) ) {
468  $params['img-class'] .= ( $params['img-class'] !== '' ? ' ' : '' ) . 'thumbborder';
469  }
470  }
471  $params = self::getImageLinkMTOParams( $frameParams, $query, $parser ) + $params;
472  $s = $thumb->toHtml( $params );
473  }
474 
475  if ( $enableLegacyMediaDOM ) {
476  if ( $frameParams['align'] != '' ) {
478  'div',
479  [ 'class' => 'float' . $frameParams['align'] ],
480  $s
481  );
482  }
483  return str_replace( "\n", ' ', $prefix . $s . $postfix );
484  }
485 
486  $wrapper = 'span';
487  $caption = '';
488 
489  if ( $frameParams['align'] != '' ) {
490  $wrapper = 'figure';
491  // Possible values: mw-halign-left mw-halign-center mw-halign-right mw-halign-none
492  $classes[] = "mw-halign-{$frameParams['align']}";
493  $caption = Html::rawElement(
494  'figcaption', [], $frameParams['caption'] ?? ''
495  );
496  } elseif ( isset( $frameParams['valign'] ) ) {
497  // Possible values: mw-valign-middle mw-valign-baseline mw-valign-sub
498  // mw-valign-super mw-valign-top mw-valign-text-top mw-valign-bottom
499  // mw-valign-text-bottom
500  $classes[] = "mw-valign-{$frameParams['valign']}";
501  }
502 
503  if ( isset( $frameParams['border'] ) ) {
504  $classes[] = 'mw-image-border';
505  }
506 
507  if ( isset( $frameParams['class'] ) ) {
508  $classes[] = $frameParams['class'];
509  }
510 
511  $attribs = [
512  'class' => $classes,
513  'typeof' => $rdfaType,
514  ];
515 
516  $s = Html::rawElement( $wrapper, $attribs, $s . $caption );
517 
518  return str_replace( "\n", ' ', $s );
519  }
520 
529  private static function getImageLinkMTOParams( $frameParams, $query = '', $parser = null ) {
530  $mtoParams = [];
531  if ( isset( $frameParams['link-url'] ) && $frameParams['link-url'] !== '' ) {
532  $mtoParams['custom-url-link'] = $frameParams['link-url'];
533  if ( isset( $frameParams['link-target'] ) ) {
534  $mtoParams['custom-target-link'] = $frameParams['link-target'];
535  }
536  if ( $parser ) {
537  $extLinkAttrs = $parser->getExternalLinkAttribs( $frameParams['link-url'] );
538  foreach ( $extLinkAttrs as $name => $val ) {
539  // Currently could include 'rel' and 'target'
540  $mtoParams['parser-extlink-' . $name] = $val;
541  }
542  }
543  } elseif ( isset( $frameParams['link-title'] ) && $frameParams['link-title'] !== '' ) {
544  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
545  $mtoParams['custom-title-link'] = Title::newFromLinkTarget(
546  $linkRenderer->normalizeTarget( $frameParams['link-title'] )
547  );
548  } elseif ( !empty( $frameParams['no-link'] ) ) {
549  // No link
550  } else {
551  $mtoParams['desc-link'] = true;
552  $mtoParams['desc-query'] = $query;
553  }
554  return $mtoParams;
555  }
556 
569  public static function makeThumbLinkObj(
570  LinkTarget $title, $file, $label = '', $alt = '', $align = null,
571  $params = [], $framed = false, $manualthumb = ""
572  ) {
573  $frameParams = [
574  'alt' => $alt,
575  'caption' => $label,
576  'align' => $align
577  ];
578  if ( $framed ) {
579  $frameParams['framed'] = true;
580  }
581  if ( $manualthumb ) {
582  $frameParams['manualthumb'] = $manualthumb;
583  }
584  $classes = [ 'mw-default-size' ];
585  return self::makeThumbLink2(
586  $title, $file, $frameParams, $params, false, '', $classes
587  );
588  }
589 
600  public static function makeThumbLink2(
601  LinkTarget $title, $file, $frameParams = [], $handlerParams = [],
602  $time = false, $query = "", array $classes = []
603  ) {
604  $exists = $file && $file->exists();
605 
606  $services = MediaWikiServices::getInstance();
607  $enableLegacyMediaDOM = $services->getMainConfig()->get( 'ParserEnableLegacyMediaDOM' );
608 
609  $page = $handlerParams['page'] ?? false;
610  if ( !isset( $frameParams['align'] ) ) {
611  $frameParams['align'] = '';
612  if ( $enableLegacyMediaDOM ) {
613  $frameParams['align'] = 'right';
614  }
615  }
616  if ( !isset( $frameParams['alt'] ) ) {
617  $frameParams['alt'] = '';
618  }
619  if ( !isset( $frameParams['title'] ) ) {
620  $frameParams['title'] = '';
621  }
622  if ( !isset( $frameParams['caption'] ) ) {
623  $frameParams['caption'] = '';
624  }
625 
626  if ( empty( $handlerParams['width'] ) ) {
627  // Reduce width for upright images when parameter 'upright' is used
628  $handlerParams['width'] = isset( $frameParams['upright'] ) ? 130 : 180;
629  }
630 
631  $thumb = false;
632  $noscale = false;
633  $manualthumb = false;
634  $rdfaType = null;
635 
636  if ( !$exists ) {
637  $outerWidth = $handlerParams['width'] + 2;
638  } else {
639  if ( isset( $frameParams['manualthumb'] ) ) {
640  # Use manually specified thumbnail
641  $manual_title = Title::makeTitleSafe( NS_FILE, $frameParams['manualthumb'] );
642  if ( $manual_title ) {
643  $manual_img = $services->getRepoGroup()
644  ->findFile( $manual_title );
645  if ( $manual_img ) {
646  $thumb = $manual_img->getUnscaledThumb( $handlerParams );
647  $manualthumb = true;
648  } else {
649  $exists = false;
650  }
651  }
652  } elseif ( isset( $frameParams['framed'] ) ) {
653  // Use image dimensions, don't scale
654  $thumb = $file->getUnscaledThumb( $handlerParams );
655  $noscale = true;
656  $rdfaType = '/Frame';
657  } else {
658  # Do not present an image bigger than the source, for bitmap-style images
659  # This is a hack to maintain compatibility with arbitrary pre-1.10 behavior
660  $srcWidth = $file->getWidth( $page );
661  if ( $srcWidth && !$file->mustRender() && $handlerParams['width'] > $srcWidth ) {
662  $handlerParams['width'] = $srcWidth;
663  }
664  $thumb = $file->transform( $handlerParams );
665  }
666 
667  if ( $thumb ) {
668  $outerWidth = $thumb->getWidth() + 2;
669  } else {
670  $outerWidth = $handlerParams['width'] + 2;
671  }
672  }
673 
674  $url = Title::newFromLinkTarget( $title )->getLocalURL( $query );
675 
676  if ( $page ) {
677  # ThumbnailImage::toHtml() already adds page= onto the end of DjVu URLs
678  # So we don't need to pass it here in $query. However, the URL for the
679  # zoom icon still needs it, so we make a unique query for it. See T16771
680  # Also needed for the "resource" when manualthumb is set
681  # FIXME: What about "lang" and other querystring parameters
682  $url = wfAppendQuery( $url, [ 'page' => $page ] );
683  }
684 
685  if ( $manualthumb
686  && !isset( $frameParams['link-title'] )
687  && !isset( $frameParams['link-url'] )
688  && !isset( $frameParams['no-link'] ) ) {
689  $frameParams['link-url'] = $url;
690  }
691 
692  if ( $frameParams['align'] != '' ) {
693  // Possible values: mw-halign-left mw-halign-center mw-halign-right mw-halign-none
694  $classes[] = "mw-halign-{$frameParams['align']}";
695  }
696 
697  if ( isset( $frameParams['class'] ) ) {
698  $classes[] = $frameParams['class'];
699  }
700 
701  $s = '';
702 
703  if ( $enableLegacyMediaDOM ) {
704  $s .= "<div class=\"thumb t{$frameParams['align']}\">"
705  . "<div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
706  }
707 
708  if ( !$exists ) {
709  $label = '';
710  if ( $enableLegacyMediaDOM ) {
711  // This is the information for tooltips for inline images which
712  // Parsoid stores in data-mw. See T273014
713  $label = $frameParams['title'];
714  }
716  $title, $label, '', '', '', (bool)$time, $handlerParams
717  );
718  $zoomIcon = '';
719  } elseif ( !$thumb ) {
720  if ( $enableLegacyMediaDOM ) {
721  $s .= wfMessage( 'thumbnail_error', '' )->escaped();
722  } else {
724  $title, '', '', '', '', (bool)$time, $handlerParams
725  );
726  }
727  $zoomIcon = '';
728  } else {
729  if ( !$noscale && !$manualthumb ) {
730  self::processResponsiveImages( $file, $thumb, $handlerParams );
731  }
732  $params = [
733  'alt' => $frameParams['alt'],
734  'title' => $frameParams['title'],
735  ];
736  if ( $enableLegacyMediaDOM ) {
737  $params += [
738  'img-class' => ( isset( $frameParams['class'] ) && $frameParams['class'] !== ''
739  ? $frameParams['class'] . ' '
740  : '' ) . 'thumbimage'
741  ];
742  }
743  if ( $manualthumb ) {
744  $params['resource'] = $url;
745  }
746  $params = self::getImageLinkMTOParams( $frameParams, $query ) + $params;
747  $s .= $thumb->toHtml( $params );
748  if ( isset( $frameParams['framed'] ) ) {
749  $zoomIcon = "";
750  } else {
751  $zoomIcon = Html::rawElement( 'div', [ 'class' => 'magnify' ],
752  Html::rawElement( 'a', [
753  'href' => $url,
754  'class' => 'internal',
755  'title' => wfMessage( 'thumbnail-more' )->text() ],
756  "" ) );
757  }
758  }
759 
760  if ( $enableLegacyMediaDOM ) {
761  $s .= ' <div class="thumbcaption">' . $zoomIcon . $frameParams['caption'] . "</div></div></div>";
762  return str_replace( "\n", ' ', $s );
763  }
764 
765  $s .= Html::rawElement(
766  'figcaption', [], $frameParams['caption'] ?? ''
767  );
768 
769  $rdfaType = $rdfaType ?: '/Thumb';
770 
771  switch ( $file ? $file->getMediaType() : '' ) {
772  case 'AUDIO':
773  $rdfaType = 'mw:Audio' . $rdfaType;
774  break;
775  case 'VIDEO':
776  $rdfaType = 'mw:Video' . $rdfaType;
777  break;
778  default:
779  $rdfaType = 'mw:Image' . $rdfaType;
780  }
781 
782  if ( !$exists || !$thumb ) {
783  $rdfaType = 'mw:Error ' . $rdfaType;
784  }
785 
786  $attribs = [
787  'class' => $classes,
788  'typeof' => $rdfaType,
789  ];
790 
791  $s = Html::rawElement( 'figure', $attribs, $s );
792 
793  return str_replace( "\n", ' ', $s );
794  }
795 
804  public static function processResponsiveImages( $file, $thumb, $hp ) {
805  $responsiveImages = MediaWikiServices::getInstance()->getMainConfig()->get( 'ResponsiveImages' );
806  if ( $responsiveImages && $thumb && !$thumb->isError() ) {
807  $hp15 = $hp;
808  $hp15['width'] = round( $hp['width'] * 1.5 );
809  $hp20 = $hp;
810  $hp20['width'] = $hp['width'] * 2;
811  if ( isset( $hp['height'] ) ) {
812  $hp15['height'] = round( $hp['height'] * 1.5 );
813  $hp20['height'] = $hp['height'] * 2;
814  }
815 
816  $thumb15 = $file->transform( $hp15 );
817  $thumb20 = $file->transform( $hp20 );
818  if ( $thumb15 && !$thumb15->isError() && $thumb15->getUrl() !== $thumb->getUrl() ) {
819  $thumb->responsiveUrls['1.5'] = $thumb15->getUrl();
820  }
821  if ( $thumb20 && !$thumb20->isError() && $thumb20->getUrl() !== $thumb->getUrl() ) {
822  $thumb->responsiveUrls['2'] = $thumb20->getUrl();
823  }
824  }
825  }
826 
840  public static function makeBrokenImageLinkObj(
841  $title, $label = '', $query = '', $unused1 = '', $unused2 = '',
842  $time = false, array $handlerParams = []
843  ) {
844  if ( !$title instanceof LinkTarget ) {
845  wfWarn( __METHOD__ . ': Requires $title to be a LinkTarget object.' );
846  return "<!-- ERROR -->" . htmlspecialchars( $label );
847  }
848 
850  $services = MediaWikiServices::getInstance();
851  $mainConfig = $services->getMainConfig();
852  $enableUploads = $mainConfig->get( 'EnableUploads' );
853  $uploadMissingFileUrl = $mainConfig->get( 'UploadMissingFileUrl' );
854  $uploadNavigationUrl = $mainConfig->get( 'UploadNavigationUrl' );
855  if ( $label == '' ) {
856  $label = $title->getPrefixedText();
857  }
858 
859  $html = Html::element( 'span', [
860  // These data attributes are used to dynamically size the span, see T273013
861  'data-width' => $handlerParams['width'] ?? null,
862  'data-height' => $handlerParams['height'] ?? null,
863  ], $label );
864 
865  if ( $mainConfig->get( 'ParserEnableLegacyMediaDOM' ) ) {
866  $html = htmlspecialchars( $label );
867  }
868 
869  $repoGroup = $services->getRepoGroup();
870  $currentExists = $time
871  && $repoGroup->findFile( $title ) !== false;
872 
873  if ( ( $uploadMissingFileUrl || $uploadNavigationUrl || $enableUploads )
874  && !$currentExists
875  ) {
876  if ( $repoGroup->getLocalRepo()->checkRedirect( $title ) ) {
877  // We already know it's a redirect, so mark it accordingly
878  return self::link(
879  $title,
880  $html,
881  [ 'class' => 'mw-redirect' ],
882  wfCgiToArray( $query ),
883  [ 'known', 'noclasses' ]
884  );
885  }
886  return Html::rawElement( 'a', [
887  'href' => self::getUploadUrl( $title, $query ),
888  'class' => 'new',
889  'title' => $title->getPrefixedText()
890  ], $html );
891  }
892  return self::link(
893  $title,
894  $html,
895  [],
896  wfCgiToArray( $query ),
897  [ 'known', 'noclasses' ]
898  );
899  }
900 
909  protected static function getUploadUrl( $destFile, $query = '' ) {
910  $mainConfig = MediaWikiServices::getInstance()->getMainConfig();
911  $uploadMissingFileUrl = $mainConfig->get( 'UploadMissingFileUrl' );
912  $uploadNavigationUrl = $mainConfig->get( 'UploadNavigationUrl' );
913  $q = 'wpDestFile=' . Title::castFromLinkTarget( $destFile )->getPartialURL();
914  if ( $query != '' ) {
915  $q .= '&' . $query;
916  }
917 
918  if ( $uploadMissingFileUrl ) {
919  return wfAppendQuery( $uploadMissingFileUrl, $q );
920  }
921 
922  if ( $uploadNavigationUrl ) {
923  return wfAppendQuery( $uploadNavigationUrl, $q );
924  }
925 
926  $upload = SpecialPage::getTitleFor( 'Upload' );
927 
928  return $upload->getLocalURL( $q );
929  }
930 
940  public static function makeMediaLinkObj( $title, $html = '', $time = false ) {
941  $img = MediaWikiServices::getInstance()->getRepoGroup()->findFile(
942  $title, [ 'time' => $time ]
943  );
944  return self::makeMediaLinkFile( $title, $img, $html );
945  }
946 
959  public static function makeMediaLinkFile( LinkTarget $title, $file, $html = '' ) {
960  if ( $file && $file->exists() ) {
961  $url = $file->getUrl();
962  $class = 'internal';
963  } else {
964  $url = self::getUploadUrl( $title );
965  $class = 'new';
966  }
967 
968  $alt = $title->getText();
969  if ( $html == '' ) {
970  $html = $alt;
971  }
972 
973  $ret = '';
974  $attribs = [
975  'href' => $url,
976  'class' => $class,
977  'title' => $alt
978  ];
979 
980  if ( !Hooks::runner()->onLinkerMakeMediaLinkFile(
981  Title::castFromLinkTarget( $title ), $file, $html, $attribs, $ret )
982  ) {
983  wfDebug( "Hook LinkerMakeMediaLinkFile changed the output of link "
984  . "with url {$url} and text {$html} to {$ret}" );
985  return $ret;
986  }
987 
988  return Html::rawElement( 'a', $attribs, $html );
989  }
990 
1001  public static function specialLink( $name, $key = '' ) {
1002  if ( $key == '' ) {
1003  $key = strtolower( $name );
1004  }
1005 
1006  return self::linkKnown( SpecialPage::getTitleFor( $name ), wfMessage( $key )->escaped() );
1007  }
1008 
1027  public static function makeExternalLink( $url, $text, $escape = true,
1028  $linktype = '', $attribs = [], $title = null
1029  ) {
1030  global $wgTitle;
1031  $class = "external";
1032  if ( $linktype ) {
1033  $class .= " $linktype";
1034  }
1035  if ( isset( $attribs['class'] ) && $attribs['class'] ) {
1036  $class .= " {$attribs['class']}";
1037  }
1038  $attribs['class'] = $class;
1039 
1040  if ( $escape ) {
1041  $text = htmlspecialchars( $text );
1042  }
1043 
1044  if ( !$title ) {
1045  $title = $wgTitle;
1046  }
1047  $newRel = Parser::getExternalLinkRel( $url, $title );
1048  if ( !isset( $attribs['rel'] ) || $attribs['rel'] === '' ) {
1049  $attribs['rel'] = $newRel;
1050  } elseif ( $newRel !== '' ) {
1051  // Merge the rel attributes.
1052  $newRels = explode( ' ', $newRel );
1053  $oldRels = explode( ' ', $attribs['rel'] );
1054  $combined = array_unique( array_merge( $newRels, $oldRels ) );
1055  $attribs['rel'] = implode( ' ', $combined );
1056  }
1057  $link = '';
1058  $success = Hooks::runner()->onLinkerMakeExternalLink(
1059  $url, $text, $link, $attribs, $linktype );
1060  if ( !$success ) {
1061  wfDebug( "Hook LinkerMakeExternalLink changed the output of link "
1062  . "with url {$url} and text {$text} to {$link}" );
1063  return $link;
1064  }
1065  $attribs['href'] = $url;
1066  return Html::rawElement( 'a', $attribs, $text );
1067  }
1068 
1080  public static function userLink( $userId, $userName, $altUserName = false ) {
1081  if ( $userName === '' || $userName === false || $userName === null ) {
1082  wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1083  'that need to be fixed?' );
1084  return wfMessage( 'empty-username' )->parse();
1085  }
1086 
1087  $classes = 'mw-userlink';
1088  $page = null;
1089  if ( $userId == 0 ) {
1090  $page = ExternalUserNames::getUserLinkTitle( $userName );
1091 
1092  if ( ExternalUserNames::isExternal( $userName ) ) {
1093  $classes .= ' mw-extuserlink';
1094  } elseif ( $altUserName === false ) {
1095  $altUserName = IPUtils::prettifyIP( $userName );
1096  }
1097  $classes .= ' mw-anonuserlink'; // Separate link class for anons (T45179)
1098  } else {
1099  $page = TitleValue::tryNew( NS_USER, strtr( $userName, ' ', '_' ) );
1100  }
1101 
1102  // Wrap the output with <bdi> tags for directionality isolation
1103  $linkText =
1104  '<bdi>' . htmlspecialchars( $altUserName !== false ? $altUserName : $userName ) . '</bdi>';
1105 
1106  return $page
1107  ? self::link( $page, $linkText, [ 'class' => $classes ] )
1108  : Html::rawElement( 'span', [ 'class' => $classes ], $linkText );
1109  }
1110 
1125  public static function userToolLinks(
1126  $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits = null,
1127  $useParentheses = true
1128  ) {
1129  if ( $userText === '' ) {
1130  wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1131  'that need to be fixed?' );
1132  return ' ' . wfMessage( 'empty-username' )->parse();
1133  }
1134  global $wgLang;
1135  $disableAnonTalk = MediaWikiServices::getInstance()->getMainConfig()->get( 'DisableAnonTalk' );
1136  $talkable = !( $disableAnonTalk && $userId == 0 );
1137  $blockable = !( $flags & self::TOOL_LINKS_NOBLOCK );
1138  $addEmailLink = $flags & self::TOOL_LINKS_EMAIL && $userId;
1139 
1140  if ( $userId == 0 && ExternalUserNames::isExternal( $userText ) ) {
1141  // No tools for an external user
1142  return '';
1143  }
1144 
1145  $items = [];
1146  if ( $talkable ) {
1147  $items[] = self::userTalkLink( $userId, $userText );
1148  }
1149  if ( $userId ) {
1150  // check if the user has an edit
1151  $attribs = [];
1152  $attribs['class'] = 'mw-usertoollinks-contribs';
1153  if ( $redContribsWhenNoEdits ) {
1154  if ( intval( $edits ) === 0 && $edits !== 0 ) {
1155  $user = User::newFromId( $userId );
1156  $edits = $user->getEditCount();
1157  }
1158  if ( $edits === 0 ) {
1159  $attribs['class'] .= ' new';
1160  }
1161  }
1162  $contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText );
1163 
1164  $items[] = self::link( $contribsPage, wfMessage( 'contribslink' )->escaped(), $attribs );
1165  }
1166  $userCanBlock = RequestContext::getMain()->getAuthority()->isAllowed( 'block' );
1167  if ( $blockable && $userCanBlock ) {
1168  $items[] = self::blockLink( $userId, $userText );
1169  }
1170 
1171  $user = RequestContext::getMain()->getUser();
1172  if ( $addEmailLink && $user->canSendEmail() ) {
1173  $items[] = self::emailLink( $userId, $userText );
1174  }
1175 
1176  Hooks::runner()->onUserToolLinksEdit( $userId, $userText, $items );
1177 
1178  if ( !$items ) {
1179  return '';
1180  }
1181 
1182  if ( $useParentheses ) {
1183  return wfMessage( 'word-separator' )->escaped()
1184  . '<span class="mw-usertoollinks">'
1185  . wfMessage( 'parentheses' )->rawParams( $wgLang->pipeList( $items ) )->escaped()
1186  . '</span>';
1187  }
1188 
1189  $tools = [];
1190  foreach ( $items as $tool ) {
1191  $tools[] = Html::rawElement( 'span', [], $tool );
1192  }
1193  return ' <span class="mw-usertoollinks mw-changeslist-links">' .
1194  implode( ' ', $tools ) . '</span>';
1195  }
1196 
1206  public static function userToolLinksRedContribs(
1207  $userId, $userText, $edits = null, $useParentheses = true
1208  ) {
1209  return self::userToolLinks( $userId, $userText, true, 0, $edits, $useParentheses );
1210  }
1211 
1218  public static function userTalkLink( $userId, $userText ) {
1219  if ( $userText === '' ) {
1220  wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1221  'that need to be fixed?' );
1222  return wfMessage( 'empty-username' )->parse();
1223  }
1224 
1225  $userTalkPage = TitleValue::tryNew( NS_USER_TALK, strtr( $userText, ' ', '_' ) );
1226  $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-talk' ];
1227  $linkText = wfMessage( 'talkpagelinktext' )->escaped();
1228 
1229  return $userTalkPage
1230  ? self::link( $userTalkPage, $linkText, $moreLinkAttribs )
1231  : Html::rawElement( 'span', $moreLinkAttribs, $linkText );
1232  }
1233 
1240  public static function blockLink( $userId, $userText ) {
1241  if ( $userText === '' ) {
1242  wfDebug( __METHOD__ . ' received an empty username. Are there database errors ' .
1243  'that need to be fixed?' );
1244  return wfMessage( 'empty-username' )->parse();
1245  }
1246 
1247  $blockPage = SpecialPage::getTitleFor( 'Block', $userText );
1248  $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-block' ];
1249 
1250  return self::link( $blockPage,
1251  wfMessage( 'blocklink' )->escaped(),
1252  $moreLinkAttribs
1253  );
1254  }
1255 
1261  public static function emailLink( $userId, $userText ) {
1262  if ( $userText === '' ) {
1263  wfLogWarning( __METHOD__ . ' received an empty username. Are there database errors ' .
1264  'that need to be fixed?' );
1265  return wfMessage( 'empty-username' )->parse();
1266  }
1267 
1268  $emailPage = SpecialPage::getTitleFor( 'Emailuser', $userText );
1269  $moreLinkAttribs = [ 'class' => 'mw-usertoollinks-mail' ];
1270  return self::link( $emailPage,
1271  wfMessage( 'emaillink' )->escaped(),
1272  $moreLinkAttribs
1273  );
1274  }
1275 
1287  public static function revUserLink( RevisionRecord $revRecord, $isPublic = false ) {
1288  // TODO inject authority
1289  $authority = RequestContext::getMain()->getAuthority();
1290 
1291  if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) && $isPublic ) {
1292  $link = wfMessage( 'rev-deleted-user' )->escaped();
1293  } elseif ( $revRecord->userCan( RevisionRecord::DELETED_USER, $authority ) ) {
1294  $revUser = $revRecord->getUser( RevisionRecord::FOR_THIS_USER, $authority );
1295  $link = self::userLink(
1296  $revUser ? $revUser->getId() : 0,
1297  $revUser ? $revUser->getName() : ''
1298  );
1299  } else {
1300  $link = wfMessage( 'rev-deleted-user' )->escaped();
1301  }
1302  if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
1303  $class = self::getRevisionDeletedClass( $revRecord );
1304  return '<span class="' . $class . '">' . $link . '</span>';
1305  }
1306  return $link;
1307  }
1308 
1315  public static function getRevisionDeletedClass( RevisionRecord $revisionRecord ): string {
1316  $class = 'history-deleted';
1317  if ( $revisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
1318  $class .= ' mw-history-suppressed';
1319  }
1320  return $class;
1321  }
1322 
1335  public static function revUserTools(
1336  RevisionRecord $revRecord,
1337  $isPublic = false,
1338  $useParentheses = true
1339  ) {
1340  // TODO inject authority
1341  $authority = RequestContext::getMain()->getAuthority();
1342 
1343  if ( $revRecord->userCan( RevisionRecord::DELETED_USER, $authority ) &&
1344  ( !$revRecord->isDeleted( RevisionRecord::DELETED_USER ) || !$isPublic )
1345  ) {
1346  $revUser = $revRecord->getUser( RevisionRecord::FOR_THIS_USER, $authority );
1347  $userId = $revUser ? $revUser->getId() : 0;
1348  $userText = $revUser ? $revUser->getName() : '';
1349 
1350  if ( $userId || $userText !== '' ) {
1351  $link = self::userLink( $userId, $userText )
1352  . self::userToolLinks( $userId, $userText, false, 0, null,
1353  $useParentheses );
1354  }
1355  }
1356 
1357  if ( !isset( $link ) ) {
1358  $link = wfMessage( 'rev-deleted-user' )->escaped();
1359  }
1360 
1361  if ( $revRecord->isDeleted( RevisionRecord::DELETED_USER ) ) {
1362  $class = self::getRevisionDeletedClass( $revRecord );
1363  return ' <span class="' . $class . ' mw-userlink">' . $link . '</span>';
1364  }
1365  return $link;
1366  }
1367 
1378  public static function expandLocalLinks( string $html ) {
1379  $formatter = new HtmlFormatter( $html );
1380  $doc = $formatter->getDoc();
1381  $xpath = new DOMXPath( $doc );
1382  $nodes = $xpath->query( '//a[@href]' );
1384  foreach ( $nodes as $node ) {
1385  $node->setAttribute(
1386  'href',
1387  wfExpandUrl( $node->getAttribute( 'href' ), PROTO_RELATIVE )
1388  );
1389  }
1390  return $formatter->getText( 'html' );
1391  }
1392 
1413  public static function formatComment(
1414  $comment, $title = null, $local = false, $wikiId = null
1415  ) {
1416  $formatter = MediaWikiServices::getInstance()->getCommentFormatter();
1417  return $formatter->format( $comment, $title, $local, $wikiId );
1418  }
1419 
1439  public static function formatLinksInComment(
1440  $comment, $title = null, $local = false, $wikiId = null
1441  ) {
1442  $formatter = MediaWikiServices::getInstance()->getCommentFormatter();
1443  return $formatter->formatLinksUnsafe( $comment, $title, $local, $wikiId );
1444  }
1445 
1452  public static function normalizeSubpageLink( $contextTitle, $target, &$text ) {
1453  # Valid link forms:
1454  # Foobar -- normal
1455  # :Foobar -- override special treatment of prefix (images, language links)
1456  # /Foobar -- convert to CurrentPage/Foobar
1457  # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial and final / from text
1458  # ../ -- convert to CurrentPage, from CurrentPage/CurrentSubPage
1459  # ../Foobar -- convert to CurrentPage/Foobar,
1460  # (from CurrentPage/CurrentSubPage)
1461  # ../Foobar/ -- convert to CurrentPage/Foobar, use 'Foobar' as text
1462  # (from CurrentPage/CurrentSubPage)
1463 
1464  $ret = $target; # default return value is no change
1465 
1466  # Some namespaces don't allow subpages,
1467  # so only perform processing if subpages are allowed
1468  if (
1469  $contextTitle && MediaWikiServices::getInstance()->getNamespaceInfo()->
1470  hasSubpages( $contextTitle->getNamespace() )
1471  ) {
1472  $hash = strpos( $target, '#' );
1473  if ( $hash !== false ) {
1474  $suffix = substr( $target, $hash );
1475  $target = substr( $target, 0, $hash );
1476  } else {
1477  $suffix = '';
1478  }
1479  # T9425
1480  $target = trim( $target );
1481  $contextPrefixedText = MediaWikiServices::getInstance()->getTitleFormatter()->
1482  getPrefixedText( $contextTitle );
1483  # Look at the first character
1484  if ( $target != '' && $target[0] === '/' ) {
1485  # / at end means we don't want the slash to be shown
1486  $m = [];
1487  $trailingSlashes = preg_match_all( '%(/+)$%', $target, $m );
1488  if ( $trailingSlashes ) {
1489  $noslash = $target = substr( $target, 1, -strlen( $m[0][0] ) );
1490  } else {
1491  $noslash = substr( $target, 1 );
1492  }
1493 
1494  $ret = $contextPrefixedText . '/' . trim( $noslash ) . $suffix;
1495  if ( $text === '' ) {
1496  $text = $target . $suffix;
1497  } # this might be changed for ugliness reasons
1498  } else {
1499  # check for .. subpage backlinks
1500  $dotdotcount = 0;
1501  $nodotdot = $target;
1502  while ( strncmp( $nodotdot, "../", 3 ) == 0 ) {
1503  ++$dotdotcount;
1504  $nodotdot = substr( $nodotdot, 3 );
1505  }
1506  if ( $dotdotcount > 0 ) {
1507  $exploded = explode( '/', $contextPrefixedText );
1508  if ( count( $exploded ) > $dotdotcount ) { # not allowed to go below top level page
1509  $ret = implode( '/', array_slice( $exploded, 0, -$dotdotcount ) );
1510  # / at the end means don't show full path
1511  if ( substr( $nodotdot, -1, 1 ) === '/' ) {
1512  $nodotdot = rtrim( $nodotdot, '/' );
1513  if ( $text === '' ) {
1514  $text = $nodotdot . $suffix;
1515  }
1516  }
1517  $nodotdot = trim( $nodotdot );
1518  if ( $nodotdot != '' ) {
1519  $ret .= '/' . $nodotdot;
1520  }
1521  $ret .= $suffix;
1522  }
1523  }
1524  }
1525  }
1526 
1527  return $ret;
1528  }
1529 
1549  public static function commentBlock(
1550  $comment, $title = null, $local = false, $wikiId = null, $useParentheses = true
1551  ) {
1552  return MediaWikiServices::getInstance()->getCommentFormatter()
1553  ->formatBlock( $comment, $title, $local, $wikiId, $useParentheses );
1554  }
1555 
1571  public static function revComment(
1572  RevisionRecord $revRecord,
1573  $local = false,
1574  $isPublic = false,
1575  $useParentheses = true
1576  ) {
1577  $authority = RequestContext::getMain()->getAuthority();
1578  $formatter = MediaWikiServices::getInstance()->getCommentFormatter();
1579  return $formatter->formatRevision( $revRecord, $authority, $local, $isPublic, $useParentheses );
1580  }
1581 
1587  public static function formatRevisionSize( $size ) {
1588  if ( $size == 0 ) {
1589  $stxt = wfMessage( 'historyempty' )->escaped();
1590  } else {
1591  $stxt = wfMessage( 'nbytes' )->numParams( $size )->escaped();
1592  }
1593  return "<span class=\"history-size mw-diff-bytes\" data-mw-bytes=\"$size\">$stxt</span>";
1594  }
1595 
1602  public static function tocIndent() {
1603  return "\n<ul>\n";
1604  }
1605 
1613  public static function tocUnindent( $level ) {
1614  return "</li>\n" . str_repeat( "</ul>\n</li>\n", $level > 0 ? $level : 0 );
1615  }
1616 
1628  public static function tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex = false ) {
1629  $classes = "toclevel-$level";
1630  if ( $sectionIndex !== false ) {
1631  $classes .= " tocsection-$sectionIndex";
1632  }
1633 
1634  // <li class="$classes"><a href="#$anchor"><span class="tocnumber">
1635  // $tocnumber</span> <span class="toctext">$tocline</span></a>
1636  return Html::openElement( 'li', [ 'class' => $classes ] )
1637  . Html::rawElement( 'a',
1638  [ 'href' => "#$anchor" ],
1639  Html::element( 'span', [ 'class' => 'tocnumber' ], $tocnumber )
1640  . ' '
1641  . Html::rawElement( 'span', [ 'class' => 'toctext' ], $tocline )
1642  );
1643  }
1644 
1652  public static function tocLineEnd() {
1653  return "</li>\n";
1654  }
1655 
1664  public static function tocList( $toc, Language $lang = null ) {
1665  $lang = $lang ?? RequestContext::getMain()->getLanguage();
1666 
1667  $title = wfMessage( 'toc' )->inLanguage( $lang )->escaped();
1668 
1669  return '<div id="toc" class="toc" role="navigation" aria-labelledby="mw-toc-heading">'
1670  . Html::element( 'input', [
1671  'type' => 'checkbox',
1672  'role' => 'button',
1673  'id' => 'toctogglecheckbox',
1674  'class' => 'toctogglecheckbox',
1675  'style' => 'display:none',
1676  ] )
1677  . Html::openElement( 'div', [
1678  'class' => 'toctitle',
1679  'lang' => $lang->getHtmlCode(),
1680  'dir' => $lang->getDir(),
1681  ] )
1682  . '<h2 id="mw-toc-heading">' . $title . '</h2>'
1683  . '<span class="toctogglespan">'
1684  . Html::label( '', 'toctogglecheckbox', [
1685  'class' => 'toctogglelabel',
1686  ] )
1687  . '</span>'
1688  . "</div>"
1689  . $toc
1690  . "</ul>\n</div>\n";
1691  }
1692 
1701  public static function generateTOC( $tree, Language $lang = null ) {
1702  $toc = '';
1703  $lastLevel = 0;
1704  foreach ( $tree as $section ) {
1705  if ( $section['toclevel'] > $lastLevel ) {
1706  $toc .= self::tocIndent();
1707  } elseif ( $section['toclevel'] < $lastLevel ) {
1708  $toc .= self::tocUnindent(
1709  $lastLevel - $section['toclevel'] );
1710  } else {
1711  $toc .= self::tocLineEnd();
1712  }
1713 
1714  $toc .= self::tocLine( $section['anchor'],
1715  $section['line'], $section['number'],
1716  $section['toclevel'], $section['index'] );
1717  $lastLevel = $section['toclevel'];
1718  }
1719  $toc .= self::tocLineEnd();
1720  return self::tocList( $toc, $lang );
1721  }
1722 
1739  public static function makeHeadline( $level, $attribs, $anchor, $html,
1740  $link, $fallbackAnchor = false
1741  ) {
1742  $anchorEscaped = htmlspecialchars( $anchor );
1743  $fallback = '';
1744  if ( $fallbackAnchor !== false && $fallbackAnchor !== $anchor ) {
1745  $fallbackAnchor = htmlspecialchars( $fallbackAnchor );
1746  $fallback = "<span id=\"$fallbackAnchor\"></span>";
1747  }
1748  return "<h$level$attribs"
1749  . "$fallback<span class=\"mw-headline\" id=\"$anchorEscaped\">$html</span>"
1750  . $link
1751  . "</h$level>";
1752  }
1753 
1760  public static function splitTrail( $trail ) {
1761  $regex = MediaWikiServices::getInstance()->getContentLanguage()->linkTrail();
1762  $inside = '';
1763  if ( $trail !== '' && preg_match( $regex, $trail, $m ) ) {
1764  list( , $inside, $trail ) = $m;
1765  }
1766  return [ $inside, $trail ];
1767  }
1768 
1798  public static function generateRollback(
1799  RevisionRecord $revRecord,
1800  IContextSource $context = null,
1801  $options = [ 'verify' ]
1802  ) {
1803  if ( $context === null ) {
1804  $context = RequestContext::getMain();
1805  }
1806 
1807  $editCount = false;
1808  if ( in_array( 'verify', $options, true ) ) {
1809  $editCount = self::getRollbackEditCount( $revRecord, true );
1810  if ( $editCount === false ) {
1811  return '';
1812  }
1813  }
1814 
1815  $inner = self::buildRollbackLink( $revRecord, $context, $editCount );
1816 
1817  // Allow extensions to modify the rollback link.
1818  // Abort further execution if the extension wants full control over the link.
1819  if ( !Hooks::runner()->onLinkerGenerateRollbackLink(
1820  $revRecord, $context, $options, $inner ) ) {
1821  return $inner;
1822  }
1823 
1824  if ( !in_array( 'noBrackets', $options, true ) ) {
1825  $inner = $context->msg( 'brackets' )->rawParams( $inner )->escaped();
1826  }
1827 
1828  if ( MediaWikiServices::getInstance()->getUserOptionsLookup()
1829  ->getBoolOption( $context->getUser(), 'showrollbackconfirmation' )
1830  ) {
1831  $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
1832  $stats->increment( 'rollbackconfirmation.event.load' );
1833  $context->getOutput()->addModules( 'mediawiki.misc-authed-curate' );
1834  }
1835 
1836  return '<span class="mw-rollback-link">' . $inner . '</span>';
1837  }
1838 
1857  public static function getRollbackEditCount( RevisionRecord $revRecord, $verify ) {
1858  $showRollbackEditCount = MediaWikiServices::getInstance()->getMainConfig()->get( 'ShowRollbackEditCount' );
1859 
1860  if ( !is_int( $showRollbackEditCount ) || !$showRollbackEditCount > 0 ) {
1861  // Nothing has happened, indicate this by returning 'null'
1862  return null;
1863  }
1864 
1865  $dbr = wfGetDB( DB_REPLICA );
1866 
1867  // Up to the value of $wgShowRollbackEditCount revisions are counted
1868  $revQuery = MediaWikiServices::getInstance()->getRevisionStore()->getQueryInfo();
1869  // T270033 Index renaming
1870  $revIndex = $dbr->indexExists( 'revision', 'page_timestamp', __METHOD__ )
1871  ? 'page_timestamp'
1872  : 'rev_page_timestamp';
1873  $res = $dbr->select(
1874  $revQuery['tables'],
1875  [ 'rev_user_text' => $revQuery['fields']['rev_user_text'], 'rev_deleted' ],
1876  [ 'rev_page' => $revRecord->getPageId() ],
1877  __METHOD__,
1878  [
1879  'USE INDEX' => [ 'revision' => $revIndex ],
1880  'ORDER BY' => 'rev_timestamp DESC',
1881  'LIMIT' => $showRollbackEditCount + 1
1882  ],
1883  $revQuery['joins']
1884  );
1885 
1886  $revUser = $revRecord->getUser( RevisionRecord::RAW );
1887  $revUserText = $revUser ? $revUser->getName() : '';
1888 
1889  $editCount = 0;
1890  $moreRevs = false;
1891  foreach ( $res as $row ) {
1892  if ( $row->rev_user_text != $revUserText ) {
1893  if ( $verify &&
1894  ( $row->rev_deleted & RevisionRecord::DELETED_TEXT
1895  || $row->rev_deleted & RevisionRecord::DELETED_USER
1896  ) ) {
1897  // If the user or the text of the revision we might rollback
1898  // to is deleted in some way we can't rollback. Similar to
1899  // the checks in WikiPage::commitRollback.
1900  return false;
1901  }
1902  $moreRevs = true;
1903  break;
1904  }
1905  $editCount++;
1906  }
1907 
1908  if ( $verify && $editCount <= $showRollbackEditCount && !$moreRevs ) {
1909  // We didn't find at least $wgShowRollbackEditCount revisions made by the current user
1910  // and there weren't any other revisions. That means that the current user is the only
1911  // editor, so we can't rollback
1912  return false;
1913  }
1914  return $editCount;
1915  }
1916 
1931  public static function buildRollbackLink(
1932  RevisionRecord $revRecord,
1933  IContextSource $context = null,
1934  $editCount = false
1935  ) {
1936  $showRollbackEditCount = MediaWikiServices::getInstance()->getMainConfig()->get( 'ShowRollbackEditCount' );
1937  $miserMode = MediaWikiServices::getInstance()->getMainConfig()->get( 'MiserMode' );
1938  // To config which pages are affected by miser mode
1939  $disableRollbackEditCountSpecialPage = [ 'Recentchanges', 'Watchlist' ];
1940 
1941  if ( $context === null ) {
1942  $context = RequestContext::getMain();
1943  }
1944 
1945  $title = $revRecord->getPageAsLinkTarget();
1946  $revUser = $revRecord->getUser();
1947  $revUserText = $revUser ? $revUser->getName() : '';
1948 
1949  $query = [
1950  'action' => 'rollback',
1951  'from' => $revUserText,
1952  'token' => $context->getUser()->getEditToken( 'rollback' ),
1953  ];
1954 
1955  $attrs = [
1956  'data-mw' => 'interface',
1957  'title' => $context->msg( 'tooltip-rollback' )->text()
1958  ];
1959 
1960  $options = [ 'known', 'noclasses' ];
1961 
1962  if ( $context->getRequest()->getBool( 'bot' ) ) {
1963  // T17999
1964  $query['hidediff'] = '1';
1965  $query['bot'] = '1';
1966  }
1967 
1968  $disableRollbackEditCount = false;
1969  if ( $miserMode ) {
1970  foreach ( $disableRollbackEditCountSpecialPage as $specialPage ) {
1971  if ( $context->getTitle()->isSpecial( $specialPage ) ) {
1972  $disableRollbackEditCount = true;
1973  break;
1974  }
1975  }
1976  }
1977 
1978  if ( !$disableRollbackEditCount
1979  && is_int( $showRollbackEditCount )
1980  && $showRollbackEditCount > 0
1981  ) {
1982  if ( !is_numeric( $editCount ) ) {
1983  $editCount = self::getRollbackEditCount( $revRecord, false );
1984  }
1985 
1986  // The edit count can be 0 on replica lag, fall back to the generic rollbacklink message
1987  if ( $editCount > 0 ) {
1988  if ( $editCount > $showRollbackEditCount ) {
1989  $html = $context->msg( 'rollbacklinkcount-morethan' )
1990  ->numParams( $showRollbackEditCount )->parse();
1991  } else {
1992  $html = $context->msg( 'rollbacklinkcount' )->numParams( $editCount )->parse();
1993  }
1994 
1995  return self::link( $title, $html, $attrs, $query, $options );
1996  }
1997  }
1998 
1999  $html = $context->msg( 'rollbacklink' )->escaped();
2000  return self::link( $title, $html, $attrs, $query, $options );
2001  }
2002 
2011  public static function formatHiddenCategories( $hiddencats ) {
2012  $outText = '';
2013  if ( count( $hiddencats ) > 0 ) {
2014  # Construct the HTML
2015  $outText = '<div class="mw-hiddenCategoriesExplanation">';
2016  $outText .= wfMessage( 'hiddencategories' )->numParams( count( $hiddencats ) )->parseAsBlock();
2017  $outText .= "</div><ul>\n";
2018 
2019  foreach ( $hiddencats as $titleObj ) {
2020  # If it's hidden, it must exist - no need to check with a LinkBatch
2021  $outText .= '<li>'
2022  . self::link( $titleObj, null, [], [], 'known' )
2023  . "</li>\n";
2024  }
2025  $outText .= '</ul>';
2026  }
2027  return $outText;
2028  }
2029 
2046  public static function titleAttrib( $name, $options = null, array $msgParams = [] ) {
2047  $message = wfMessage( "tooltip-$name", $msgParams );
2048  if ( !$message->exists() ) {
2049  $tooltip = false;
2050  } else {
2051  $tooltip = $message->text();
2052  # Compatibility: formerly some tooltips had [alt-.] hardcoded
2053  $tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip );
2054  # Message equal to '-' means suppress it.
2055  if ( $tooltip == '-' ) {
2056  $tooltip = false;
2057  }
2058  }
2059 
2060  $options = (array)$options;
2061 
2062  if ( in_array( 'nonexisting', $options ) ) {
2063  $tooltip = wfMessage( 'red-link-title', $tooltip ?: '' )->text();
2064  }
2065  if ( in_array( 'withaccess', $options ) ) {
2066  $accesskey = self::accesskey( $name );
2067  if ( $accesskey !== false ) {
2068  // Should be build the same as in jquery.accessKeyLabel.js
2069  if ( $tooltip === false || $tooltip === '' ) {
2070  $tooltip = wfMessage( 'brackets', $accesskey )->text();
2071  } else {
2072  $tooltip .= wfMessage( 'word-separator' )->text();
2073  $tooltip .= wfMessage( 'brackets', $accesskey )->text();
2074  }
2075  }
2076  }
2077 
2078  return $tooltip;
2079  }
2080 
2081  public static $accesskeycache;
2082 
2094  public static function accesskey( $name ) {
2095  if ( isset( self::$accesskeycache[$name] ) ) {
2096  return self::$accesskeycache[$name];
2097  }
2098 
2099  $message = wfMessage( "accesskey-$name" );
2100 
2101  if ( !$message->exists() ) {
2102  $accesskey = false;
2103  } else {
2104  $accesskey = $message->plain();
2105  if ( $accesskey === '' || $accesskey === '-' ) {
2106  # Per standard MW behavior, a value of '-' means to suppress the
2107  # attribute. It is thus forbidden to use this as an access key.
2108  $accesskey = false;
2109  }
2110  }
2111 
2112  self::$accesskeycache[$name] = $accesskey;
2113  return self::$accesskeycache[$name];
2114  }
2115 
2130  public static function getRevDeleteLink(
2131  Authority $performer,
2132  RevisionRecord $revRecord,
2134  ) {
2135  $canHide = $performer->isAllowed( 'deleterevision' );
2136  $canHideHistory = $performer->isAllowed( 'deletedhistory' );
2137  if ( !$canHide && !( $revRecord->getVisibility() && $canHideHistory ) ) {
2138  return '';
2139  }
2140 
2141  if ( !$revRecord->userCan( RevisionRecord::DELETED_RESTRICTED, $performer ) ) {
2142  return self::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
2143  }
2144  $prefixedDbKey = MediaWikiServices::getInstance()->getTitleFormatter()->
2145  getPrefixedDBkey( $title );
2146  if ( $revRecord->getId() ) {
2147  // RevDelete links using revision ID are stable across
2148  // page deletion and undeletion; use when possible.
2149  $query = [
2150  'type' => 'revision',
2151  'target' => $prefixedDbKey,
2152  'ids' => $revRecord->getId()
2153  ];
2154  } else {
2155  // Older deleted entries didn't save a revision ID.
2156  // We have to refer to these by timestamp, ick!
2157  $query = [
2158  'type' => 'archive',
2159  'target' => $prefixedDbKey,
2160  'ids' => $revRecord->getTimestamp()
2161  ];
2162  }
2163  return self::revDeleteLink(
2164  $query,
2165  $revRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ),
2166  $canHide
2167  );
2168  }
2169 
2182  public static function revDeleteLink( $query = [], $restricted = false, $delete = true ) {
2183  $sp = SpecialPage::getTitleFor( 'Revisiondelete' );
2184  $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
2185  $html = wfMessage( $msgKey )->escaped();
2186  $tag = $restricted ? 'strong' : 'span';
2187  $link = self::link( $sp, $html, [], $query, [ 'known', 'noclasses' ] );
2188  return Xml::tags(
2189  $tag,
2190  [ 'class' => 'mw-revdelundel-link' ],
2191  wfMessage( 'parentheses' )->rawParams( $link )->escaped()
2192  );
2193  }
2194 
2206  public static function revDeleteLinkDisabled( $delete = true ) {
2207  $msgKey = $delete ? 'rev-delundel' : 'rev-showdeleted';
2208  $html = wfMessage( $msgKey )->escaped();
2209  $htmlParentheses = wfMessage( 'parentheses' )->rawParams( $html )->escaped();
2210  return Xml::tags( 'span', [ 'class' => 'mw-revdelundel-link' ], $htmlParentheses );
2211  }
2212 
2225  public static function tooltipAndAccesskeyAttribs(
2226  $name,
2227  array $msgParams = [],
2228  $options = null
2229  ) {
2230  $options = (array)$options;
2231  $options[] = 'withaccess';
2232  $tooltipTitle = $name;
2233 
2234  // @since 1.35 - add a WatchlistExpiry feature flag to show new watchstar tooltip message
2235  $skin = RequestContext::getMain()->getSkin();
2236  $isWatchlistExpiryEnabled = $skin->getConfig()->get( 'WatchlistExpiry' );
2237  if ( $name === 'ca-unwatch' && $isWatchlistExpiryEnabled ) {
2238  $watchStore = MediaWikiServices::getInstance()->getWatchedItemStore();
2239  $watchedItem = $watchStore->getWatchedItem( $skin->getUser(),
2240  $skin->getRelevantTitle() );
2241  if ( $watchedItem instanceof WatchedItem && $watchedItem->getExpiry() !== null ) {
2242  $diffInDays = $watchedItem->getExpiryInDays();
2243 
2244  if ( $diffInDays ) {
2245  $msgParams = [ $diffInDays ];
2246  // Resolves to tooltip-ca-unwatch-expiring message
2247  $tooltipTitle = 'ca-unwatch-expiring';
2248  } else { // Resolves to tooltip-ca-unwatch-expiring-hours message
2249  $tooltipTitle = 'ca-unwatch-expiring-hours';
2250  }
2251 
2252  }
2253  }
2254 
2255  $attribs = [
2256  'title' => self::titleAttrib( $tooltipTitle, $options, $msgParams ),
2257  'accesskey' => self::accesskey( $name )
2258  ];
2259  if ( $attribs['title'] === false ) {
2260  unset( $attribs['title'] );
2261  }
2262  if ( $attribs['accesskey'] === false ) {
2263  unset( $attribs['accesskey'] );
2264  }
2265  return $attribs;
2266  }
2267 
2275  public static function tooltip( $name, $options = null ) {
2276  $tooltip = self::titleAttrib( $name, $options );
2277  if ( $tooltip === false ) {
2278  return '';
2279  }
2280  return Xml::expandAttributes( [
2281  'title' => $tooltip
2282  ] );
2283  }
2284 
2285 }
Linker\getImageLinkMTOParams
static getImageLinkMTOParams( $frameParams, $query='', $parser=null)
Get the link parameters for MediaTransformOutput::toHtml() from given frame parameters supplied by th...
Definition: Linker.php:529
MediaWiki\Revision\RevisionRecord\getUser
getUser( $audience=self::FOR_PUBLIC, Authority $performer=null)
Fetch revision's author's user identity, if it's available to the specified audience.
Definition: RevisionRecord.php:389
User\newFromId
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:636
Linker\makeThumbLinkObj
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:569
HtmlArmor
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:30
Linker\formatLinksInComment
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:1439
Linker
Some internal bits split of from Skin.php.
Definition: Linker.php:39
MediaWiki\Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:47
Linker\TOOL_LINKS_NOBLOCK
const TOOL_LINKS_NOBLOCK
Flags for userToolLinks()
Definition: Linker.php:43
Linker\userTalkLink
static userTalkLink( $userId, $userText)
Definition: Linker.php:1218
Xml\expandAttributes
static expandAttributes( $attribs)
Given an array of ('attributename' => 'value'), it generates the code to set the XML attributes : att...
Definition: Xml.php:68
Linker\emailLink
static emailLink( $userId, $userText)
Definition: Linker.php:1261
Linker\generateTOC
static generateTOC( $tree, Language $lang=null)
Generate a table of contents from a section tree.
Definition: Linker.php:1701
Linker\makeBrokenImageLinkObj
static makeBrokenImageLinkObj( $title, $label='', $query='', $unused1='', $unused2='', $time=false, array $handlerParams=[])
Make a "broken" link to an image.
Definition: Linker.php:840
Linker\revUserTools
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:1335
Linker\userLink
static userLink( $userId, $userName, $altUserName=false)
Make user link (or user contributions for unregistered users)
Definition: Linker.php:1080
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:203
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:37
Linker\makeThumbLink2
static makeThumbLink2(LinkTarget $title, $file, $frameParams=[], $handlerParams=[], $time=false, $query="", array $classes=[])
Definition: Linker.php:600
Linker\makeSelfLinkObj
static makeSelfLinkObj( $nt, $html='', $query='', $trail='', $prefix='')
Make appropriate markup for a link to the current article.
Definition: Linker.php:162
Linker\getUploadUrl
static getUploadUrl( $destFile, $query='')
Get the URL to upload a certain file.
Definition: Linker.php:909
Linker\tocIndent
static tocIndent()
Add another level to the Table of Contents.
Definition: Linker.php:1602
$fallback
$fallback
Definition: MessagesAb.php:11
Linker\userToolLinks
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:1125
Linker\fnamePart
static fnamePart( $url)
Returns the filename part of an url.
Definition: Linker.php:221
MediaWiki\Revision\RevisionRecord\getPageId
getPageId( $wikiId=self::LOCAL)
Get the page ID.
Definition: RevisionRecord.php:335
Linker\specialLink
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:1001
$file
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
Parser\getTargetLanguage
getTargetLanguage()
Get the target language for the content being parsed.
Definition: Parser.php:1147
Linker\linkKnown
static linkKnown( $target, $html=null, $customAttribs=[], $query=[], $options=[ 'known'])
Identical to link(), except $options defaults to 'known'.
Definition: Linker.php:140
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1167
SpecialPage\getTitleFor
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,...
Definition: SpecialPage.php:131
Linker\processResponsiveImages
static processResponsiveImages( $file, $thumb, $hp)
Process responsive images: add 1.5x and 2x subimages to the thumbnail, where applicable.
Definition: Linker.php:804
Linker\revUserLink
static revUserLink(RevisionRecord $revRecord, $isPublic=false)
Generate a user link if the current user is allowed to view it.
Definition: Linker.php:1287
wfLogWarning
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
Definition: GlobalFunctions.php:1056
$success
$success
Definition: NoLocalSettings.php:42
Linker\getInvalidTitleDescription
static getInvalidTitleDescription(IContextSource $context, $namespace, $title)
Get a message saying that an invalid title was encountered.
Definition: Linker.php:186
Parser\getExternalLinkRel
static getExternalLinkRel( $url=false, LinkTarget $title=null)
Get the rel attribute for a particular external link.
Definition: Parser.php:2209
$res
$res
Definition: testCompression.php:57
$revQuery
$revQuery
Definition: testCompression.php:56
Linker\tocLine
static tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex=false)
parameter level defines if we are on an indentation level
Definition: Linker.php:1628
$wgLang
$wgLang
Definition: Setup.php:861
Linker\tocList
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:1664
Linker\formatHiddenCategories
static formatHiddenCategories( $hiddencats)
Returns HTML for the "hidden categories on this page" list.
Definition: Linker.php:2011
Linker\tooltipAndAccesskeyAttribs
static tooltipAndAccesskeyAttribs( $name, array $msgParams=[], $options=null)
Returns the attributes for the tooltip and access key.
Definition: Linker.php:2225
NS_MAIN
const NS_MAIN
Definition: Defines.php:64
$dbr
$dbr
Definition: testCompression.php:54
wfAppendQuery
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
Definition: GlobalFunctions.php:422
WatchedItem\getExpiry
getExpiry(?int $style=TS_MW)
When the watched item will expire.
Definition: WatchedItem.php:150
Linker\$accesskeycache
static $accesskeycache
Definition: Linker.php:2081
PROTO_RELATIVE
const PROTO_RELATIVE
Definition: Defines.php:194
Linker\makeMediaLinkObj
static makeMediaLinkObj( $title, $html='', $time=false)
Create a direct link to a given uploaded file.
Definition: Linker.php:940
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
Definition: GlobalFunctions.php:997
MessageLocalizer\msg
msg( $key,... $params)
This is the method for getting translated interface messages.
Linker\buildRollbackLink
static buildRollbackLink(RevisionRecord $revRecord, IContextSource $context=null, $editCount=false)
Build a raw rollback link, useful for collections of "tool" links.
Definition: Linker.php:1931
TitleValue\tryNew
static tryNew( $namespace, $title, $fragment='', $interwiki='')
Constructs a TitleValue, or returns null if the parameters are not valid.
Definition: TitleValue.php:80
Linker\getRevisionDeletedClass
static getRevisionDeletedClass(RevisionRecord $revisionRecord)
Returns css class of a deleted revision.
Definition: Linker.php:1315
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2186
Linker\getRollbackEditCount
static getRollbackEditCount(RevisionRecord $revRecord, $verify)
This function will return the number of revisions which a rollback would revert and,...
Definition: Linker.php:1857
Linker\makeHeadline
static makeHeadline( $level, $attribs, $anchor, $html, $link, $fallbackAnchor=false)
Create a headline for content.
Definition: Linker.php:1739
Linker\expandLocalLinks
static expandLocalLinks(string $html)
Helper function to expand local links.
Definition: Linker.php:1378
Linker\tocLineEnd
static tocLineEnd()
End a Table Of Contents line.
Definition: Linker.php:1652
wfCgiToArray
wfCgiToArray( $query)
This is the logical opposite of wfArrayToCgi(): it accepts a query string as its argument and returns...
Definition: GlobalFunctions.php:375
Linker\revDeleteLinkDisabled
static revDeleteLinkDisabled( $delete=true)
Creates a dead (show/hide) link for deleting revisions/log entries.
Definition: Linker.php:2206
$title
$title
Definition: testCompression.php:38
Linker\makeExternalLink
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition: Linker.php:1027
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
Html\label
static label( $label, $id, array $attribs=[])
Convenience function for generating a label for inputs.
Definition: Html.php:818
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:894
$wgTitle
$wgTitle
Definition: Setup.php:879
Linker\splitTrail
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:1760
Linker\generateRollback
static generateRollback(RevisionRecord $revRecord, IContextSource $context=null, $options=[ 'verify'])
Generate a rollback link for a given revision.
Definition: Linker.php:1798
MediaWiki\Permissions\Authority
This interface represents the authority associated the current execution context, such as a web reque...
Definition: Authority.php:37
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:674
Linker\revComment
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:1571
Linker\blockLink
static blockLink( $userId, $userText)
Definition: Linker.php:1240
$s
foreach( $mmfl['setupFiles'] as $fileName) if( $queue) if(empty( $mmfl['quiet'])) $s
Definition: mergeMessageFileList.php:206
Linker\normaliseSpecialPage
static normaliseSpecialPage(LinkTarget $target)
Definition: Linker.php:207
ExternalUserNames\getUserLinkTitle
static getUserLinkTitle( $userName)
Get a target Title to link a username.
Definition: ExternalUserNames.php:63
Linker\TOOL_LINKS_EMAIL
const TOOL_LINKS_EMAIL
Definition: Linker.php:44
Linker\commentBlock
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:1549
Linker\userToolLinksRedContribs
static userToolLinksRedContribs( $userId, $userText, $edits=null, $useParentheses=true)
Alias for userToolLinks( $userId, $userText, true );.
Definition: Linker.php:1206
MediaWiki\Revision\RevisionRecord\getPageAsLinkTarget
getPageAsLinkTarget()
Returns the title of the page this revision is associated with as a LinkTarget object.
Definition: RevisionRecord.php:355
Xml\tags
static tags( $element, $attribs, $contents)
Same as Xml::element(), but does not escape contents.
Definition: Xml.php:133
Hooks\runner
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
Definition: Hooks.php:173
Linker\link
static link( $target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition: Linker.php:88
WatchedItem
Representation of a pair of user and title for watchlist entries.
Definition: WatchedItem.php:36
Linker\formatRevisionSize
static formatRevisionSize( $size)
Definition: Linker.php:1587
NS_USER
const NS_USER
Definition: Defines.php:66
Parser
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition: Parser.php:91
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:484
DummyLinker
Definition: DummyLinker.php:9
Title\newFromLinkTarget
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:289
Linker\makeMediaLinkFile
static makeMediaLinkFile(LinkTarget $title, $file, $html='')
Create a direct link to a given uploaded file.
Definition: Linker.php:959
Linker\titleAttrib
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:2046
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:58
Linker\formatComment
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:1413
$userOptionsLookup
UserOptionsLookup $userOptionsLookup
Definition: ApiWatchlistTrait.php:33
Linker\tooltip
static tooltip( $name, $options=null)
Returns raw bits of HTML, use titleAttrib()
Definition: Linker.php:2275
MediaWiki\Permissions\Authority\isAllowed
isAllowed(string $permission)
Checks whether this authority has the given permission in general.
MediaWiki\Revision\RevisionRecord\getId
getId( $wikiId=self::LOCAL)
Get revision ID.
Definition: RevisionRecord.php:279
MediaWiki\Revision\RevisionRecord\isDeleted
isDeleted( $field)
MCR migration note: this replaced Revision::isDeleted.
Definition: RevisionRecord.php:437
Linker\tocUnindent
static tocUnindent( $level)
Finish one or more sublevels on the Table of Contents.
Definition: Linker.php:1613
MediaWiki\Revision\RevisionRecord\userCan
userCan( $field, Authority $performer)
Determine if the give authority is allowed to view a particular field of this revision,...
Definition: RevisionRecord.php:509
Linker\makeImageLink
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
Html\openElement
static openElement( $element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing '/'...
Definition: Html.php:255
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:213
Linker\revDeleteLink
static revDeleteLink( $query=[], $restricted=false, $delete=true)
Creates a (show/hide) link for deleting revisions/log entries.
Definition: Linker.php:2182
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:67
MediaWiki\Revision\RevisionRecord\getTimestamp
getTimestamp()
MCR migration note: this replaced Revision::getTimestamp.
Definition: RevisionRecord.php:459
Linker\normalizeSubpageLink
static normalizeSubpageLink( $contextTitle, $target, &$text)
Definition: Linker.php:1452
wfWarn
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
Definition: GlobalFunctions.php:1043
Title\castFromLinkTarget
static castFromLinkTarget( $linkTarget)
Same as newFromLinkTarget, but if passed null, returns null.
Definition: Title.php:313
Html\element
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:235
NS_FILE
const NS_FILE
Definition: Defines.php:70
MediaWiki\Linker\LinkTarget
Definition: LinkTarget.php:26
Linker\accesskey
static accesskey( $name)
Given the id of an interface element, constructs the appropriate accesskey attribute from the system ...
Definition: Linker.php:2094
Linker\getRevDeleteLink
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:2130
ExternalUserNames\isExternal
static isExternal( $username)
Tells whether the username is external or not.
Definition: ExternalUserNames.php:149
Linker\makeExternalImage
static makeExternalImage( $url, $alt='')
Return the code for images which were added via external links, via Parser::maybeMakeExternalImage().
Definition: Linker.php:241
Language
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition: Language.php:42
MediaWiki\Revision\RevisionRecord\getVisibility
getVisibility()
Get the deletion bitfield of the revision.
Definition: RevisionRecord.php:448
wfExpandUrl
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
Definition: GlobalFunctions.php:474