MediaWiki  master
ChangesList.php
Go to the documentation of this file.
1 <?php
26 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
35 use OOUI\IconWidget;
37 
38 class ChangesList extends ContextSource {
39  use ProtectedHookAccessorTrait;
40 
41  public const CSS_CLASS_PREFIX = 'mw-changeslist-';
42 
43  protected $watchlist = false;
44  protected $lastdate;
45  protected $message;
46  protected $rc_cache;
47  protected $rcCacheIndex;
48  protected $rclistOpen;
49  protected $rcMoveIndex;
50 
53 
55  protected $watchMsgCache;
56 
60  protected $linkRenderer;
61 
65  protected $commentFormatter;
66 
70  protected $formattedComments;
71 
75  protected $filterGroups;
76 
81  public function __construct( $context, array $filterGroups = [] ) {
82  $this->setContext( $context );
83  $this->preCacheMessages();
84  $this->watchMsgCache = new MapCacheLRU( 50 );
85  $this->filterGroups = $filterGroups;
86 
87  $services = MediaWikiServices::getInstance();
88  $this->linkRenderer = $services->getLinkRenderer();
89  $this->commentFormatter = $services->getRowCommentFormatter();
90  }
91 
100  public static function newFromContext( IContextSource $context, array $groups = [] ) {
101  $user = $context->getUser();
102  $sk = $context->getSkin();
103  $list = null;
104  if ( Hooks::runner()->onFetchChangesList( $user, $sk, $list, $groups ) ) {
105  $userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
106  $new = $context->getRequest()->getBool(
107  'enhanced',
108  $userOptionsLookup->getBoolOption( $user, 'usenewrc' )
109  );
110 
111  return $new ?
112  new EnhancedChangesList( $context, $groups ) :
113  new OldChangesList( $context, $groups );
114  } else {
115  return $list;
116  }
117  }
118 
130  public function recentChangesLine( &$rc, $watched = false, $linenumber = null ) {
131  throw new RuntimeException( 'recentChangesLine should be implemented' );
132  }
133 
140  protected function getHighlightsContainerDiv() {
141  $highlightColorDivs = '';
142  foreach ( [ 'none', 'c1', 'c2', 'c3', 'c4', 'c5' ] as $color ) {
143  $highlightColorDivs .= Html::rawElement(
144  'div',
145  [
146  'class' => 'mw-rcfilters-ui-highlights-color-' . $color,
147  'data-color' => $color
148  ]
149  );
150  }
151 
152  return Html::rawElement(
153  'div',
154  [ 'class' => 'mw-rcfilters-ui-highlights' ],
155  $highlightColorDivs
156  );
157  }
158 
163  public function setWatchlistDivs( $value = true ) {
164  $this->watchlist = $value;
165  }
166 
171  public function isWatchlist() {
172  return (bool)$this->watchlist;
173  }
174 
179  private function preCacheMessages() {
180  if ( !isset( $this->message ) ) {
181  $this->message = [];
182  foreach ( [
183  'cur', 'diff', 'hist', 'enhancedrc-history', 'last', 'blocklink', 'history',
184  'semicolon-separator', 'pipe-separator' ] as $msg
185  ) {
186  $this->message[$msg] = $this->msg( $msg )->escaped();
187  }
188  }
189  }
190 
197  public function recentChangesFlags( $flags, $nothing = "\u{00A0}" ) {
198  $f = '';
199  foreach (
200  array_keys( $this->getConfig()->get( MainConfigNames::RecentChangesFlags ) ) as $flag
201  ) {
202  $f .= isset( $flags[$flag] ) && $flags[$flag]
203  ? self::flag( $flag, $this->getContext() )
204  : $nothing;
205  }
206 
207  return $f;
208  }
209 
218  protected function getHTMLClasses( $rc, $watched ) {
219  $classes = [ self::CSS_CLASS_PREFIX . 'line' ];
220  $logType = $rc->mAttribs['rc_log_type'];
221 
222  if ( $logType ) {
223  $classes[] = self::CSS_CLASS_PREFIX . 'log';
224  $classes[] = Sanitizer::escapeClass( self::CSS_CLASS_PREFIX . 'log-' . $logType );
225  } else {
226  $classes[] = self::CSS_CLASS_PREFIX . 'edit';
227  $classes[] = Sanitizer::escapeClass( self::CSS_CLASS_PREFIX . 'ns' .
228  $rc->mAttribs['rc_namespace'] . '-' . $rc->mAttribs['rc_title'] );
229  }
230 
231  // Indicate watched status on the line to allow for more
232  // comprehensive styling.
233  $classes[] = $watched && $rc->mAttribs['rc_timestamp'] >= $watched
234  ? self::CSS_CLASS_PREFIX . 'line-watched'
235  : self::CSS_CLASS_PREFIX . 'line-not-watched';
236 
237  $classes = array_merge( $classes, $this->getHTMLClassesForFilters( $rc ) );
238 
239  return $classes;
240  }
241 
249  protected function getHTMLClassesForFilters( $rc ) {
250  $classes = [];
251 
252  $classes[] = Sanitizer::escapeClass( self::CSS_CLASS_PREFIX . 'ns-' .
253  $rc->mAttribs['rc_namespace'] );
254 
255  $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
256  $classes[] = Sanitizer::escapeClass(
257  self::CSS_CLASS_PREFIX .
258  'ns-' .
259  ( $nsInfo->isTalk( $rc->mAttribs['rc_namespace'] ) ? 'talk' : 'subject' )
260  );
261 
262  foreach ( $this->filterGroups as $filterGroup ) {
263  foreach ( $filterGroup->getFilters() as $filter ) {
264  $filter->applyCssClassIfNeeded( $this, $rc, $classes );
265  }
266  }
267 
268  return $classes;
269  }
270 
281  public static function flag( $flag, IContextSource $context = null ) {
282  static $map = [ 'minoredit' => 'minor', 'botedit' => 'bot' ];
283  static $flagInfos = null;
284 
285  if ( $flagInfos === null ) {
286  $recentChangesFlags = MediaWikiServices::getInstance()->getMainConfig()
287  ->get( MainConfigNames::RecentChangesFlags );
288  $flagInfos = [];
289  foreach ( $recentChangesFlags as $key => $value ) {
290  $flagInfos[$key]['letter'] = $value['letter'];
291  $flagInfos[$key]['title'] = $value['title'];
292  // Allow customized class name, fall back to flag name
293  $flagInfos[$key]['class'] = $value['class'] ?? $key;
294  }
295  }
296 
297  $context = $context ?: RequestContext::getMain();
298 
299  // Inconsistent naming, kept for b/c
300  if ( isset( $map[$flag] ) ) {
301  $flag = $map[$flag];
302  }
303 
304  $info = $flagInfos[$flag];
305  return Html::element( 'abbr', [
306  'class' => $info['class'],
307  'title' => wfMessage( $info['title'] )->setContext( $context )->text(),
308  ], wfMessage( $info['letter'] )->setContext( $context )->text() );
309  }
310 
315  public function beginRecentChangesList() {
316  $this->rc_cache = [];
317  $this->rcMoveIndex = 0;
318  $this->rcCacheIndex = 0;
319  $this->lastdate = '';
320  $this->rclistOpen = false;
321  $this->getOutput()->addModuleStyles( [
322  'mediawiki.interface.helpers.styles',
323  'mediawiki.special.changeslist'
324  ] );
325 
326  return '<div class="mw-changeslist">';
327  }
328 
332  public function initChangesListRows( $rows ) {
333  $this->getHookRunner()->onChangesListInitRows( $this, $rows );
334  $this->formattedComments = $this->commentFormatter->createBatch()
335  ->comments(
336  $this->commentFormatter->rows( $rows )
337  ->commentKey( 'rc_comment' )
338  ->namespaceField( 'rc_namespace' )
339  ->titleField( 'rc_title' )
340  ->indexField( 'rc_id' )
341  )
342  ->useBlock()
343  ->execute();
344  }
345 
356  public static function showCharacterDifference( $old, $new, IContextSource $context = null ) {
357  if ( !$context ) {
358  $context = RequestContext::getMain();
359  }
360 
361  $new = (int)$new;
362  $old = (int)$old;
363  $szdiff = $new - $old;
364 
365  $lang = $context->getLanguage();
366  $config = $context->getConfig();
367  $code = $lang->getCode();
368  static $fastCharDiff = [];
369  if ( !isset( $fastCharDiff[$code] ) ) {
370  $fastCharDiff[$code] = $config->get( MainConfigNames::MiserMode )
371  || $context->msg( 'rc-change-size' )->plain() === '$1';
372  }
373 
374  $formattedSize = $lang->formatNum( $szdiff );
375 
376  if ( !$fastCharDiff[$code] ) {
377  $formattedSize = $context->msg( 'rc-change-size', $formattedSize )->text();
378  }
379 
380  if ( abs( $szdiff ) > abs( $config->get( MainConfigNames::RCChangedSizeThreshold ) ) ) {
381  $tag = 'strong';
382  } else {
383  $tag = 'span';
384  }
385 
386  if ( $szdiff === 0 ) {
387  $formattedSizeClass = 'mw-plusminus-null';
388  } elseif ( $szdiff > 0 ) {
389  $formattedSize = '+' . $formattedSize;
390  $formattedSizeClass = 'mw-plusminus-pos';
391  } else {
392  $formattedSizeClass = 'mw-plusminus-neg';
393  }
394  $formattedSizeClass .= ' mw-diff-bytes';
395 
396  $formattedTotalSize = $context->msg( 'rc-change-size-new' )->numParams( $new )->text();
397 
398  return Html::element( $tag,
399  [ 'dir' => 'ltr', 'class' => $formattedSizeClass, 'title' => $formattedTotalSize ],
400  $formattedSize ) . $lang->getDirMark();
401  }
402 
410  public function formatCharacterDifference( RecentChange $old, RecentChange $new = null ) {
411  $oldlen = $old->mAttribs['rc_old_len'];
412 
413  if ( $new ) {
414  $newlen = $new->mAttribs['rc_new_len'];
415  } else {
416  $newlen = $old->mAttribs['rc_new_len'];
417  }
418 
419  if ( $oldlen === null || $newlen === null ) {
420  return '';
421  }
422 
423  return self::showCharacterDifference( $oldlen, $newlen, $this->getContext() );
424  }
425 
430  public function endRecentChangesList() {
431  $out = $this->rclistOpen ? "</ul>\n" : '';
432  $out .= '</div>';
433 
434  return $out;
435  }
436 
448  public static function revDateLink(
449  RevisionRecord $rev,
450  Authority $performer,
451  Language $lang,
452  $title = null
453  ) {
454  $ts = $rev->getTimestamp();
455  $time = $lang->userTime( $ts, $performer->getUser() );
456  $date = $lang->userTimeAndDate( $ts, $performer->getUser() );
457  if ( $rev->userCan( RevisionRecord::DELETED_TEXT, $performer ) ) {
458  $link = MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
459  $title ?? $rev->getPageAsLinkTarget(),
460  $date,
461  [ 'class' => 'mw-changeslist-date' ],
462  [ 'oldid' => $rev->getId() ]
463  );
464  } else {
465  $link = htmlspecialchars( $date );
466  }
467  if ( $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
468  $deletedClass = Linker::getRevisionDeletedClass( $rev );
469  $link = "<span class=\"$deletedClass mw-changeslist-date\">$link</span>";
470  }
471  return Html::element( 'span', [
472  'class' => 'mw-changeslist-time'
473  ], $time ) . $link;
474  }
475 
480  public function insertDateHeader( &$s, $rc_timestamp ) {
481  # Make date header if necessary
482  $date = $this->getLanguage()->userDate( $rc_timestamp, $this->getUser() );
483  if ( $date != $this->lastdate ) {
484  if ( $this->lastdate != '' ) {
485  $s .= "</ul>\n";
486  }
487  $s .= Xml::element( 'h4', null, $date ) . "\n<ul class=\"special\">";
488  $this->lastdate = $date;
489  $this->rclistOpen = true;
490  }
491  }
492 
499  public function insertLog( &$s, $title, $logtype, $useParentheses = true ) {
500  $page = new LogPage( $logtype );
501  $logname = $page->getName()->setContext( $this->getContext() )->text();
502  $link = $this->linkRenderer->makeKnownLink( $title, $logname, [
503  'class' => $useParentheses ? '' : 'mw-changeslist-links'
504  ] );
505  if ( $useParentheses ) {
506  $s .= $this->msg( 'parentheses' )->rawParams(
507  $link
508  )->escaped();
509  } else {
510  $s .= $link;
511  }
512  }
513 
519  public function insertDiffHist( &$s, &$rc, $unpatrolled = null ) {
520  # Diff link
521  if (
522  $rc->mAttribs['rc_type'] == RC_NEW ||
523  $rc->mAttribs['rc_type'] == RC_LOG ||
524  $rc->mAttribs['rc_type'] == RC_CATEGORIZE
525  ) {
526  $diffLink = $this->message['diff'];
527  } elseif ( !self::userCan( $rc, RevisionRecord::DELETED_TEXT, $this->getAuthority() ) ) {
528  $diffLink = $this->message['diff'];
529  } else {
530  $query = [
531  'curid' => $rc->mAttribs['rc_cur_id'],
532  'diff' => $rc->mAttribs['rc_this_oldid'],
533  'oldid' => $rc->mAttribs['rc_last_oldid']
534  ];
535 
536  $diffLink = $this->linkRenderer->makeKnownLink(
537  $rc->getTitle(),
538  new HtmlArmor( $this->message['diff'] ),
539  [ 'class' => 'mw-changeslist-diff' ],
540  $query
541  );
542  }
543  if ( $rc->mAttribs['rc_type'] == RC_CATEGORIZE ) {
544  $histLink = $this->message['hist'];
545  } else {
546  $histLink = $this->linkRenderer->makeKnownLink(
547  $rc->getTitle(),
548  new HtmlArmor( $this->message['hist'] ),
549  [ 'class' => 'mw-changeslist-history' ],
550  [
551  'curid' => $rc->mAttribs['rc_cur_id'],
552  'action' => 'history'
553  ]
554  );
555  }
556 
557  $s .= Html::rawElement( 'div', [ 'class' => 'mw-changeslist-links' ],
558  Html::rawElement( 'span', [], $diffLink ) .
559  Html::rawElement( 'span', [], $histLink )
560  ) .
561  ' <span class="mw-changeslist-separator"></span> ';
562  }
563 
574  public function getArticleLink( &$rc, $unpatrolled, $watched ) {
575  $params = [];
576  if ( $rc->getTitle()->isRedirect() ) {
577  $params = [ 'redirect' => 'no' ];
578  }
579 
580  $articlelink = $this->linkRenderer->makeLink(
581  $rc->getTitle(),
582  null,
583  [ 'class' => 'mw-changeslist-title' ],
584  $params
585  );
586  if ( static::isDeleted( $rc, RevisionRecord::DELETED_TEXT ) ) {
587  $class = 'history-deleted';
588  if ( static::isDeleted( $rc, RevisionRecord::DELETED_RESTRICTED ) ) {
589  $class .= ' mw-history-suppressed';
590  }
591  $articlelink = '<span class="' . $class . '">' . $articlelink . '</span>';
592  }
593  # To allow for boldening pages watched by this user
594  $articlelink = "<span class=\"mw-title\">{$articlelink}</span>";
595  # RTL/LTR marker
596  $articlelink .= $this->getLanguage()->getDirMark();
597 
598  # TODO: Deprecate the $s argument, it seems happily unused.
599  $s = '';
600  $this->getHookRunner()->onChangesListInsertArticleLink( $this, $articlelink,
601  $s, $rc, $unpatrolled, $watched );
602 
603  // Watchlist expiry icon.
604  $watchlistExpiry = '';
605  if ( isset( $rc->watchlistExpiry ) && $rc->watchlistExpiry ) {
606  $watchlistExpiry = $this->getWatchlistExpiry( $rc );
607  }
608 
609  return "{$s} {$articlelink}{$watchlistExpiry}";
610  }
611 
618  public function getWatchlistExpiry( RecentChange $recentChange ): string {
619  $item = WatchedItem::newFromRecentChange( $recentChange, $this->getUser() );
620  // Guard against expired items, even though they shouldn't come here.
621  if ( $item->isExpired() ) {
622  return '';
623  }
624  $daysLeftText = $item->getExpiryInDaysText( $this->getContext() );
625  // Matching widget is also created in ChangesListSpecialPage, for the legend.
626  $widget = new IconWidget( [
627  'icon' => 'clock',
628  'title' => $daysLeftText,
629  'classes' => [ 'mw-changesList-watchlistExpiry' ],
630  ] );
631  $widget->setAttributes( [
632  // Add labels for assistive technologies.
633  'role' => 'img',
634  'aria-label' => $this->msg( 'watchlist-expires-in-aria-label' )->text(),
635  // Days-left is used in resources/src/mediawiki.special.changeslist.watchlistexpiry/watchlistexpiry.js
636  'data-days-left' => $item->getExpiryInDays(),
637  ] );
638  // Add spaces around the widget (the page title is to one side,
639  // and a semicolon or opening-parenthesis to the other).
640  return " $widget ";
641  }
642 
651  public function getTimestamp( $rc ) {
652  // This uses the semi-colon separator unless there's a watchlist expiry date for the entry,
653  // because in that case the timestamp is preceded by a clock icon.
654  // A space is important after `.mw-changeslist-separator--semicolon` to make sure
655  // that whatever comes before it is distinguishable.
656  // (Otherwise your have the text of titles pushing up against the timestamp)
657  // A specific element is used for this purpose rather than styling `.mw-changeslist-date`
658  // as the `.mw-changeslist-date` class is used in a variety
659  // of other places with a different position and the information proceeding getTimestamp can vary.
660  // The `.mw-changeslist-time` class allows us to distinguish from `.mw-changeslist-date` elements that
661  // contain the full date (month, year) and adds consistency with Special:Contributions
662  // and other pages.
663  $separatorClass = $rc->watchlistExpiry ? 'mw-changeslist-separator' : 'mw-changeslist-separator--semicolon';
664  return Html::element( 'span', [ 'class' => $separatorClass ] ) . ' ' .
665  '<span class="mw-changeslist-date mw-changeslist-time">' .
666  htmlspecialchars( $this->getLanguage()->userTime(
667  $rc->mAttribs['rc_timestamp'],
668  $this->getUser()
669  ) ) . '</span> <span class="mw-changeslist-separator"></span> ';
670  }
671 
678  public function insertTimestamp( &$s, $rc ) {
679  $s .= $this->getTimestamp( $rc );
680  }
681 
688  public function insertUserRelatedLinks( &$s, &$rc ) {
689  if ( static::isDeleted( $rc, RevisionRecord::DELETED_USER ) ) {
690  $deletedClass = 'history-deleted';
691  if ( static::isDeleted( $rc, RevisionRecord::DELETED_RESTRICTED ) ) {
692  $deletedClass .= ' mw-history-suppressed';
693  }
694  $s .= ' <span class="' . $deletedClass . '">' .
695  $this->msg( 'rev-deleted-user' )->escaped() . '</span>';
696  } else {
697  $s .= $this->getLanguage()->getDirMark() . Linker::userLink( $rc->mAttribs['rc_user'],
698  $rc->mAttribs['rc_user_text'] );
699  $s .= Linker::userToolLinks(
700  $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'],
701  false, 0, null,
702  // The text content of tools is not wrapped with parentheses or "piped".
703  // This will be handled in CSS (T205581).
704  false
705  );
706  }
707  }
708 
715  public function insertLogEntry( $rc ) {
716  $formatter = LogFormatter::newFromRow( $rc->mAttribs );
717  $formatter->setContext( $this->getContext() );
718  $formatter->setShowUserToolLinks( true );
719  $mark = $this->getLanguage()->getDirMark();
720 
721  return Html::openElement( 'span', [ 'class' => 'mw-changeslist-log-entry' ] )
722  . $formatter->getActionText()
723  . " $mark"
724  . $formatter->getComment()
725  . $this->msg( 'word-separator' )->escaped()
726  . $formatter->getActionLinks()
727  . Html::closeElement( 'span' );
728  }
729 
735  public function insertComment( $rc ) {
736  if ( static::isDeleted( $rc, RevisionRecord::DELETED_COMMENT ) ) {
737  $deletedClass = 'history-deleted';
738  if ( static::isDeleted( $rc, RevisionRecord::DELETED_RESTRICTED ) ) {
739  $deletedClass .= ' mw-history-suppressed';
740  }
741  return ' <span class="' . $deletedClass . ' comment">' .
742  $this->msg( 'rev-deleted-comment' )->escaped() . '</span>';
743  } elseif ( isset( $rc->mAttribs['rc_id'] )
744  && isset( $this->formattedComments[$rc->mAttribs['rc_id']] )
745  ) {
746  return $this->formattedComments[$rc->mAttribs['rc_id']];
747  } else {
748  return $this->commentFormatter->formatBlock(
749  $rc->mAttribs['rc_comment'],
750  $rc->getTitle(),
751  // Whether section links should refer to local page (using default false)
752  false,
753  // wikid to generate links for (using default null) */
754  null,
755  // whether parentheses should be rendered as part of the message
756  false
757  );
758  }
759  }
760 
766  protected function numberofWatchingusers( $count ) {
767  if ( $count <= 0 ) {
768  return '';
769  }
770 
771  return $this->watchMsgCache->getWithSetCallback(
772  "watching-users-msg:$count",
773  function () use ( $count ) {
774  return $this->msg( 'number-of-watching-users-for-recent-changes' )
775  ->numParams( $count )->escaped();
776  }
777  );
778  }
779 
786  public static function isDeleted( $rc, $field ) {
787  return ( $rc->mAttribs['rc_deleted'] & $field ) == $field;
788  }
789 
799  public static function userCan( $rc, $field, Authority $performer = null ) {
800  $performer ??= RequestContext::getMain()->getAuthority();
801 
802  if ( $rc->mAttribs['rc_type'] == RC_LOG ) {
803  return LogEventsList::userCanBitfield( $rc->mAttribs['rc_deleted'], $field, $performer );
804  }
805 
806  return RevisionRecord::userCanBitfield( $rc->mAttribs['rc_deleted'], $field, $performer );
807  }
808 
814  protected function maybeWatchedLink( $link, $watched = false ) {
815  if ( $watched ) {
816  return '<strong class="mw-watched">' . $link . '</strong>';
817  } else {
818  return '<span class="mw-rc-unwatched">' . $link . '</span>';
819  }
820  }
821 
828  public function insertRollback( &$s, &$rc ) {
829  $this->insertPageTools( $s, $rc );
830  }
831 
840  private function insertPageTools( &$s, &$rc ) {
841  // FIXME Some page tools (e.g. thanks) might make sense for log entries.
842  if ( !in_array( $rc->mAttribs['rc_type'], [ RC_EDIT, RC_NEW ] )
843  // FIXME When would either of these not exist when type is RC_EDIT? Document.
844  || !$rc->mAttribs['rc_this_oldid']
845  || !$rc->mAttribs['rc_cur_id']
846  ) {
847  return;
848  }
849 
850  // Construct a fake revision for PagerTools. FIXME can't we just obtain the real one?
851  $title = $rc->getTitle();
852  $revRecord = new MutableRevisionRecord( $title );
853  $revRecord->setId( (int)$rc->mAttribs['rc_this_oldid'] );
854  $revRecord->setVisibility( (int)$rc->mAttribs['rc_deleted'] );
855  $user = new UserIdentityValue(
856  (int)$rc->mAttribs['rc_user'],
857  $rc->mAttribs['rc_user_text']
858  );
859  $revRecord->setUser( $user );
860 
861  $tools = new PagerTools(
862  $revRecord,
863  null,
864  // only show a rollback link on the top-most revision
865  $rc->getAttribute( 'page_latest' ) == $rc->mAttribs['rc_this_oldid']
866  && $rc->mAttribs['rc_type'] != RC_NEW,
867  $this->getHookRunner(),
868  $title,
869  $this->getContext(),
870  // @todo: Inject
871  MediaWikiServices::getInstance()->getLinkRenderer()
872  );
873 
874  $s .= $tools->toHTML();
875  }
876 
882  public function getRollback( RecentChange $rc ) {
883  $s = '';
884  $this->insertRollback( $s, $rc );
885  return $s;
886  }
887 
893  public function insertTags( &$s, &$rc, &$classes ) {
894  if ( empty( $rc->mAttribs['ts_tags'] ) ) {
895  return;
896  }
897 
898  [ $tagSummary, $newClasses ] = ChangeTags::formatSummaryRow(
899  $rc->mAttribs['ts_tags'],
900  'changeslist',
901  $this->getContext()
902  );
903  $classes = array_merge( $classes, $newClasses );
904  $s .= ' ' . $tagSummary;
905  }
906 
913  public function getTags( RecentChange $rc, array &$classes ) {
914  $s = '';
915  $this->insertTags( $s, $rc, $classes );
916  return $s;
917  }
918 
919  public function insertExtra( &$s, &$rc, &$classes ) {
920  // Empty, used for subclasses to add anything special.
921  }
922 
923  protected function showAsUnpatrolled( RecentChange $rc ) {
924  return self::isUnpatrolled( $rc, $this->getUser() );
925  }
926 
932  public static function isUnpatrolled( $rc, User $user ) {
933  if ( $rc instanceof RecentChange ) {
934  $isPatrolled = $rc->mAttribs['rc_patrolled'];
935  $rcType = $rc->mAttribs['rc_type'];
936  $rcLogType = $rc->mAttribs['rc_log_type'];
937  } else {
938  $isPatrolled = $rc->rc_patrolled;
939  $rcType = $rc->rc_type;
940  $rcLogType = $rc->rc_log_type;
941  }
942 
943  if ( !$isPatrolled ) {
944  if ( $user->useRCPatrol() ) {
945  return true;
946  }
947  if ( $user->useNPPatrol() && $rcType == RC_NEW ) {
948  return true;
949  }
950  if ( $user->useFilePatrol() && $rcLogType == 'upload' ) {
951  return true;
952  }
953  }
954 
955  return false;
956  }
957 
967  protected function isCategorizationWithoutRevision( $rcObj ) {
968  return intval( $rcObj->getAttribute( 'rc_type' ) ) === RC_CATEGORIZE
969  && intval( $rcObj->getAttribute( 'rc_this_oldid' ) ) === 0;
970  }
971 
977  protected function getDataAttributes( RecentChange $rc ) {
978  $attrs = [];
979 
980  $type = $rc->getAttribute( 'rc_source' );
981  switch ( $type ) {
984  $attrs['data-mw-revid'] = $rc->mAttribs['rc_this_oldid'];
985  break;
987  $attrs['data-mw-logid'] = $rc->mAttribs['rc_logid'];
988  $attrs['data-mw-logaction'] =
989  $rc->mAttribs['rc_log_type'] . '/' . $rc->mAttribs['rc_log_action'];
990  break;
992  $attrs['data-mw-revid'] = $rc->mAttribs['rc_this_oldid'];
993  break;
994  }
995 
996  $attrs[ 'data-mw-ts' ] = $rc->getAttribute( 'rc_timestamp' );
997 
998  return $attrs;
999  }
1000 
1008  public function setChangeLinePrefixer( callable $prefixer ) {
1009  $this->changeLinePrefixer = $prefixer;
1010  }
1011 }
getUser()
const RC_NEW
Definition: Defines.php:117
const RC_LOG
Definition: Defines.php:118
const RC_EDIT
Definition: Defines.php:116
const RC_CATEGORIZE
Definition: Defines.php:120
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
getContext()
static formatSummaryRow( $tags, $page, MessageLocalizer $localizer=null)
Creates HTML for the given tags.
Definition: ChangeTags.php:196
static newFromContext(IContextSource $context, array $groups=[])
Fetch an appropriate changes list class for the specified context Some users might want to use an enh...
maybeWatchedLink( $link, $watched=false)
setWatchlistDivs( $value=true)
Sets the list to use a "<li class='watchlist-(namespace)-(page)'>" tag.
formatCharacterDifference(RecentChange $old, RecentChange $new=null)
Format the character difference of one or several changes.
insertDateHeader(&$s, $rc_timestamp)
insertRollback(&$s, &$rc)
Insert a rollback link.
showAsUnpatrolled(RecentChange $rc)
RowCommentFormatter $commentFormatter
Definition: ChangesList.php:65
static isUnpatrolled( $rc, User $user)
getHighlightsContainerDiv()
Get the container for highlights that are used in the new StructuredFilters system.
static revDateLink(RevisionRecord $rev, Authority $performer, Language $lang, $title=null)
Render the date and time of a revision in the current user language based on whether the user is able...
recentChangesLine(&$rc, $watched=false, $linenumber=null)
Format a line.
__construct( $context, array $filterGroups=[])
Definition: ChangesList.php:81
recentChangesFlags( $flags, $nothing="\u{00A0}")
Returns the appropriate flags for new page, minor change and patrolling.
getDataAttributes(RecentChange $rc)
Get recommended data attributes for a change line.
numberofWatchingusers( $count)
Returns the string which indicates the number of watching users.
getHTMLClasses( $rc, $watched)
Get an array of default HTML class attributes for the change.
getWatchlistExpiry(RecentChange $recentChange)
Get HTML to display the clock icon for watched items that have a watchlist expiry time.
callable $changeLinePrefixer
Definition: ChangesList.php:52
getTags(RecentChange $rc, array &$classes)
getArticleLink(&$rc, $unpatrolled, $watched)
Get the HTML link to the changed page, possibly with a prefix from hook handlers, and a suffix for te...
insertUserRelatedLinks(&$s, &$rc)
Insert links to user page, user talk page and eventually a blocking link.
static showCharacterDifference( $old, $new, IContextSource $context=null)
Show formatted char difference.
getRollback(RecentChange $rc)
MapCacheLRU $watchMsgCache
Definition: ChangesList.php:55
endRecentChangesList()
Returns text for the end of RC.
static flag( $flag, IContextSource $context=null)
Make an "<abbr>" element for a given change flag.
LinkRenderer $linkRenderer
Definition: ChangesList.php:60
insertLogEntry( $rc)
Insert a formatted action.
setChangeLinePrefixer(callable $prefixer)
Sets the callable that generates a change line prefix added to the beginning of each line.
static isDeleted( $rc, $field)
Determine if said field of a revision is hidden.
const CSS_CLASS_PREFIX
Definition: ChangesList.php:41
insertLog(&$s, $title, $logtype, $useParentheses=true)
insertTags(&$s, &$rc, &$classes)
ChangesListFilterGroup[] $filterGroups
Definition: ChangesList.php:75
string[] $formattedComments
Comments indexed by rc_id.
Definition: ChangesList.php:70
insertComment( $rc)
Insert a formatted comment.
static userCan( $rc, $field, Authority $performer=null)
Determine if the current user is allowed to view a particular field of this revision,...
insertExtra(&$s, &$rc, &$classes)
initChangesListRows( $rows)
getTimestamp( $rc)
Get the timestamp from $rc formatted with current user's settings and a separator.
isCategorizationWithoutRevision( $rcObj)
Determines whether a revision is linked to this change; this may not be the case when the categorizat...
getHTMLClassesForFilters( $rc)
Get an array of CSS classes attributed to filters for this row.
insertDiffHist(&$s, &$rc, $unpatrolled=null)
insertTimestamp(&$s, $rc)
Insert time timestamp string from $rc into $s.
beginRecentChangesList()
Returns text for the start of the tabular part of RC.
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
getContext()
Get the base IContextSource object.
setContext(IContextSource $context)
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
Definition: Hooks.php:173
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:30
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:236
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:214
static openElement( $element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing '/'...
Definition: Html.php:256
static closeElement( $element)
Returns "</$element>".
Definition: Html.php:320
Base class for language-specific code.
Definition: Language.php:56
static userCanBitfield( $bitfield, $field, Authority $performer)
Determine if the current user is allowed to view a particular field of this log row,...
static newFromRow( $row)
Handy shortcut for constructing a formatter directly from database row.
Class to simplify the use of log pages.
Definition: LogPage.php:40
Handles a simple LRU key/value map with a maximum number of entries.
Definition: MapCacheLRU.php:36
This is basically a CommentFormatter with a CommentStore dependency, allowing it to retrieve comment ...
Class that generates HTML anchor link elements for pages.
Some internal bits split of from Skin.php.
Definition: Linker.php:65
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
Page revision base class.
getTimestamp()
MCR migration note: this replaced Revision::getTimestamp.
getPageAsLinkTarget()
Returns the title of the page this revision is associated with as a LinkTarget object.
userCan( $field, Authority $performer)
Determine if the give authority is allowed to view a particular field of this revision,...
isDeleted( $field)
MCR migration note: this replaced Revision::isDeleted.
getId( $wikiId=self::LOCAL)
Get revision ID.
Value object representing a user's identity.
Generate a set of tools for a revision.
Definition: PagerTools.php:13
Utility class for creating new RC entries.
const SRC_CATEGORIZE
getAttribute( $name)
Get an attribute value.
static getMain()
Get the RequestContext object associated with the main request.
static escapeClass( $class)
Given a value, escape it so that it can be used as a CSS class and return it.
Definition: Sanitizer.php:1107
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:70
useFilePatrol()
Check whether to enable new files patrol features for this user.
Definition: User.php:2406
useNPPatrol()
Check whether to enable new pages patrol features for this user.
Definition: User.php:2391
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Definition: User.php:2381
Representation of a pair of user and title for watchlist entries.
Definition: WatchedItem.php:36
getExpiryInDaysText(MessageLocalizer $msgLocalizer, $isDropdownOption=false)
Get days remaining until a watched item expires as a text.
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:43
Interface for objects which can provide a MediaWiki context on request.
This interface represents the authority associated the current execution context, such as a web reque...
Definition: Authority.php:37
getUser()
Returns the performer of the actions associated with this authority.
Result wrapper for grabbing data queried from an IDatabase object.
foreach( $mmfl['setupFiles'] as $fileName) if( $queue) if(empty( $mmfl['quiet'])) $s
if(!isset( $args[0])) $lang