26use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
38 use ProtectedHookAccessorTrait;
80 public function __construct( $context, array $filterGroups = [] ) {
82 $this->preCacheMessages();
84 $this->filterGroups = $filterGroups;
86 $services = MediaWikiServices::getInstance();
87 $this->linkRenderer = $services->getLinkRenderer();
88 $this->commentFormatter = $services->getRowCommentFormatter();
103 if ( Hooks::runner()->onFetchChangesList( $user, $sk, $list, $groups ) ) {
104 $userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
107 $userOptionsLookup->getBoolOption( $user,
'usenewrc' )
130 throw new RuntimeException(
'recentChangesLine should be implemented' );
140 $highlightColorDivs =
'';
141 foreach ( [
'none',
'c1',
'c2',
'c3',
'c4',
'c5' ] as $color ) {
142 $highlightColorDivs .= Html::rawElement(
145 'class' =>
'mw-rcfilters-ui-highlights-color-' . $color,
146 'data-color' => $color
151 return Html::rawElement(
153 [
'class' =>
'mw-rcfilters-ui-highlights' ],
163 $this->watchlist = $value;
171 return (
bool)$this->watchlist;
178 private function preCacheMessages() {
179 if ( !isset( $this->message ) ) {
182 'cur',
'diff',
'hist',
'enhancedrc-history',
'last',
'blocklink',
'history',
183 'semicolon-separator',
'pipe-separator' ] as $msg
185 $this->message[$msg] = $this->msg( $msg )->escaped();
199 array_keys( $this->
getConfig()->
get( MainConfigNames::RecentChangesFlags ) ) as $flag
201 $f .= isset( $flags[$flag] ) && $flags[$flag]
218 $classes = [ self::CSS_CLASS_PREFIX .
'line' ];
219 $logType = $rc->mAttribs[
'rc_log_type'];
222 $classes[] = self::CSS_CLASS_PREFIX .
'log';
223 $classes[] = Sanitizer::escapeClass( self::CSS_CLASS_PREFIX .
'log-' . $logType );
225 $classes[] = self::CSS_CLASS_PREFIX .
'edit';
226 $classes[] = Sanitizer::escapeClass( self::CSS_CLASS_PREFIX .
'ns' .
227 $rc->mAttribs[
'rc_namespace'] .
'-' . $rc->mAttribs[
'rc_title'] );
232 $classes[] = $watched && $rc->mAttribs[
'rc_timestamp'] >= $watched
233 ? self::CSS_CLASS_PREFIX .
'line-watched'
234 : self::CSS_CLASS_PREFIX .
'line-not-watched';
251 $classes[] = Sanitizer::escapeClass( self::CSS_CLASS_PREFIX .
'ns-' .
252 $rc->mAttribs[
'rc_namespace'] );
254 $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
255 $classes[] = Sanitizer::escapeClass(
256 self::CSS_CLASS_PREFIX .
258 ( $nsInfo->isTalk( $rc->mAttribs[
'rc_namespace'] ) ?
'talk' :
'subject' )
261 foreach ( $this->filterGroups as $filterGroup ) {
262 foreach ( $filterGroup->getFilters() as $filter ) {
263 $filter->applyCssClassIfNeeded( $this, $rc, $classes );
281 static $map = [
'minoredit' =>
'minor',
'botedit' =>
'bot' ];
282 static $flagInfos =
null;
284 if ( $flagInfos ===
null ) {
285 $recentChangesFlags = MediaWikiServices::getInstance()->getMainConfig()
286 ->get( MainConfigNames::RecentChangesFlags );
288 foreach ( $recentChangesFlags as $key => $value ) {
289 $flagInfos[$key][
'letter'] = $value[
'letter'];
290 $flagInfos[$key][
'title'] = $value[
'title'];
292 $flagInfos[$key][
'class'] = $value[
'class'] ?? $key;
296 $context = $context ?: RequestContext::getMain();
299 if ( isset( $map[$flag] ) ) {
303 $info = $flagInfos[$flag];
304 return Html::element(
'abbr', [
305 'class' => $info[
'class'],
315 $this->rc_cache = [];
316 $this->rcMoveIndex = 0;
317 $this->rcCacheIndex = 0;
318 $this->lastdate =
'';
319 $this->rclistOpen =
false;
321 'mediawiki.interface.helpers.styles',
322 'mediawiki.special.changeslist'
325 return '<div class="mw-changeslist">';
332 $this->getHookRunner()->onChangesListInitRows( $this, $rows );
333 $this->formattedComments = $this->commentFormatter->createBatch()
335 $this->commentFormatter->rows( $rows )
336 ->commentKey(
'rc_comment' )
337 ->namespaceField(
'rc_namespace' )
338 ->titleField(
'rc_title' )
339 ->indexField(
'rc_id' )
357 $context = RequestContext::getMain();
362 $szdiff = $new - $old;
364 $lang = $context->getLanguage();
365 $config = $context->getConfig();
366 $code =
$lang->getCode();
367 static $fastCharDiff = [];
368 if ( !isset( $fastCharDiff[$code] ) ) {
369 $fastCharDiff[$code] = $config->get( MainConfigNames::MiserMode )
370 || $context->msg(
'rc-change-size' )->plain() ===
'$1';
373 $formattedSize =
$lang->formatNum( $szdiff );
375 if ( !$fastCharDiff[$code] ) {
376 $formattedSize = $context->msg(
'rc-change-size', $formattedSize )->text();
379 if ( abs( $szdiff ) > abs( $config->get( MainConfigNames::RCChangedSizeThreshold ) ) ) {
385 if ( $szdiff === 0 ) {
386 $formattedSizeClass =
'mw-plusminus-null';
387 } elseif ( $szdiff > 0 ) {
388 $formattedSize =
'+' . $formattedSize;
389 $formattedSizeClass =
'mw-plusminus-pos';
391 $formattedSizeClass =
'mw-plusminus-neg';
393 $formattedSizeClass .=
' mw-diff-bytes';
395 $formattedTotalSize = $context->msg(
'rc-change-size-new' )->numParams( $new )->text();
397 return Html::element( $tag,
398 [
'dir' =>
'ltr',
'class' => $formattedSizeClass,
'title' => $formattedTotalSize ],
399 $formattedSize ) .
$lang->getDirMark();
410 $oldlen = $old->mAttribs[
'rc_old_len'];
413 $newlen = $new->mAttribs[
'rc_new_len'];
415 $newlen = $old->mAttribs[
'rc_new_len'];
418 if ( $oldlen ===
null || $newlen ===
null ) {
422 return self::showCharacterDifference( $oldlen, $newlen, $this->
getContext() );
430 $out = $this->rclistOpen ?
"</ul>\n" :
'';
455 $date =
$lang->userTimeAndDate( $ts, $performer->
getUser() );
456 if ( $rev->
userCan( RevisionRecord::DELETED_TEXT, $performer ) ) {
457 $link = MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
460 [
'class' =>
'mw-changeslist-date' ],
461 [
'oldid' => $rev->
getId() ]
464 $link = htmlspecialchars( $date );
466 if ( $rev->
isDeleted( RevisionRecord::DELETED_TEXT ) ) {
468 $link =
"<span class=\"$deletedClass mw-changeslist-date\">$link</span>";
470 return Html::element(
'span', [
471 'class' =>
'mw-changeslist-time'
480 # Make date header if necessary
482 if ( $date != $this->lastdate ) {
483 if ( $this->lastdate !=
'' ) {
486 $s .= Xml::element(
'h4',
null, $date ) .
"\n<ul class=\"special\">";
487 $this->lastdate = $date;
488 $this->rclistOpen =
true;
499 $page =
new LogPage( $logtype );
500 $logname = $page->getName()->setContext( $this->
getContext() )->text();
501 $link = $this->linkRenderer->makeKnownLink(
$title, $logname, [
502 'class' => $useParentheses ?
'' :
'mw-changeslist-links'
504 if ( $useParentheses ) {
505 $s .= $this->
msg(
'parentheses' )->rawParams(
521 $rc->mAttribs[
'rc_type'] ==
RC_NEW ||
522 $rc->mAttribs[
'rc_type'] ==
RC_LOG ||
525 $diffLink = $this->message[
'diff'];
526 } elseif ( !self::userCan( $rc, RevisionRecord::DELETED_TEXT, $this->
getAuthority() ) ) {
527 $diffLink = $this->message[
'diff'];
530 'curid' => $rc->mAttribs[
'rc_cur_id'],
531 'diff' => $rc->mAttribs[
'rc_this_oldid'],
532 'oldid' => $rc->mAttribs[
'rc_last_oldid']
535 $diffLink = $this->linkRenderer->makeKnownLink(
538 [
'class' =>
'mw-changeslist-diff' ],
543 $histLink = $this->message[
'hist'];
545 $histLink = $this->linkRenderer->makeKnownLink(
548 [
'class' =>
'mw-changeslist-history' ],
550 'curid' => $rc->mAttribs[
'rc_cur_id'],
551 'action' =>
'history'
556 $s .= Html::rawElement(
'div', [
'class' =>
'mw-changeslist-links' ],
557 Html::rawElement(
'span', [], $diffLink ) .
558 Html::rawElement(
'span', [], $histLink )
560 ' <span class="mw-changeslist-separator"></span> ';
575 if ( $rc->getTitle()->isRedirect() ) {
576 $params = [
'redirect' =>
'no' ];
579 $articlelink = $this->linkRenderer->makeLink(
582 [
'class' =>
'mw-changeslist-title' ],
585 if ( static::isDeleted( $rc, RevisionRecord::DELETED_TEXT ) ) {
586 $class =
'history-deleted';
587 if ( static::isDeleted( $rc, RevisionRecord::DELETED_RESTRICTED ) ) {
588 $class .=
' mw-history-suppressed';
590 $articlelink =
'<span class="' . $class .
'">' . $articlelink .
'</span>';
592 # To allow for boldening pages watched by this user
593 $articlelink =
"<span class=\"mw-title\">{$articlelink}</span>";
595 $articlelink .= $this->
getLanguage()->getDirMark();
597 # TODO: Deprecate the $s argument, it seems happily unused.
599 $this->getHookRunner()->onChangesListInsertArticleLink( $this, $articlelink,
600 $s, $rc, $unpatrolled, $watched );
603 $watchlistExpiry =
'';
604 if ( isset( $rc->watchlistExpiry ) && $rc->watchlistExpiry ) {
608 return "{$s} {$articlelink}{$watchlistExpiry}";
620 if ( $item->isExpired() ) {
625 $widget =
new IconWidget( [
627 'title' => $daysLeftText,
628 'classes' => [
'mw-changesList-watchlistExpiry' ],
630 $widget->setAttributes( [
633 'aria-label' => $this->msg(
'watchlist-expires-in-aria-label' )->text(),
635 'data-days-left' => $item->getExpiryInDays(),
662 $separatorClass = $rc->watchlistExpiry ?
'mw-changeslist-separator' :
'mw-changeslist-separator--semicolon';
663 return Html::element(
'span', [
'class' => $separatorClass ] ) .
' ' .
664 '<span class="mw-changeslist-date mw-changeslist-time">' .
665 htmlspecialchars( $this->getLanguage()->userTime(
666 $rc->mAttribs[
'rc_timestamp'],
668 ) ) .
'</span> <span class="mw-changeslist-separator"></span> ';
688 if ( static::isDeleted( $rc, RevisionRecord::DELETED_USER ) ) {
689 $deletedClass =
'history-deleted';
690 if ( static::isDeleted( $rc, RevisionRecord::DELETED_RESTRICTED ) ) {
691 $deletedClass .=
' mw-history-suppressed';
693 $s .=
' <span class="' . $deletedClass .
'">' .
694 $this->msg(
'rev-deleted-user' )->escaped() .
'</span>';
696 $s .= $this->getLanguage()->getDirMark() .
Linker::userLink( $rc->mAttribs[
'rc_user'],
697 $rc->mAttribs[
'rc_user_text'] );
699 $rc->mAttribs[
'rc_user'], $rc->mAttribs[
'rc_user_text'],
715 $formatter = LogFormatter::newFromRow( $rc->mAttribs );
716 $formatter->setContext( $this->
getContext() );
717 $formatter->setShowUserToolLinks(
true );
718 $mark = $this->getLanguage()->getDirMark();
720 return Html::openElement(
'span', [
'class' =>
'mw-changeslist-log-entry' ] )
721 . $formatter->getActionText()
723 . $formatter->getComment()
724 . $this->msg(
'word-separator' )->escaped()
725 . $formatter->getActionLinks()
726 . Html::closeElement(
'span' );
735 if ( static::isDeleted( $rc, RevisionRecord::DELETED_COMMENT ) ) {
736 $deletedClass =
'history-deleted';
737 if ( static::isDeleted( $rc, RevisionRecord::DELETED_RESTRICTED ) ) {
738 $deletedClass .=
' mw-history-suppressed';
740 return ' <span class="' . $deletedClass .
' comment">' .
741 $this->msg(
'rev-deleted-comment' )->escaped() .
'</span>';
742 } elseif ( isset( $rc->mAttribs[
'rc_id'] )
743 && isset( $this->formattedComments[$rc->mAttribs[
'rc_id']] )
745 return $this->formattedComments[$rc->mAttribs[
'rc_id']];
747 return $this->commentFormatter->formatBlock(
748 $rc->mAttribs[
'rc_comment'],
770 return $this->watchMsgCache->getWithSetCallback(
771 "watching-users-msg:$count",
772 function () use ( $count ) {
773 return $this->msg(
'number-of-watching-users-for-recent-changes' )
774 ->numParams( $count )->escaped();
786 return ( $rc->mAttribs[
'rc_deleted'] & $field ) == $field;
799 if ( $performer ===
null ) {
800 $performer = RequestContext::getMain()->getAuthority();
803 if ( $rc->mAttribs[
'rc_type'] ==
RC_LOG ) {
804 return LogEventsList::userCanBitfield( $rc->mAttribs[
'rc_deleted'], $field, $performer );
807 return RevisionRecord::userCanBitfield( $rc->mAttribs[
'rc_deleted'], $field, $performer );
817 return '<strong class="mw-watched">' . $link .
'</strong>';
819 return '<span class="mw-rc-unwatched">' . $link .
'</span>';
830 if ( $rc->mAttribs[
'rc_type'] ==
RC_EDIT
831 && $rc->mAttribs[
'rc_this_oldid']
832 && $rc->mAttribs[
'rc_cur_id']
833 && $rc->getAttribute(
'page_latest' ) == $rc->mAttribs[
'rc_this_oldid']
841 $revRecord->setId( (
int)$rc->mAttribs[
'rc_this_oldid'] );
842 $revRecord->setVisibility( (
int)$rc->mAttribs[
'rc_deleted'] );
844 (
int)$rc->mAttribs[
'rc_user'],
845 $rc->mAttribs[
'rc_user_text']
847 $revRecord->setUser( $user );
866 $this->insertRollback(
$s, $rc );
876 if ( empty( $rc->mAttribs[
'ts_tags'] ) ) {
881 $rc->mAttribs[
'ts_tags'],
885 $classes = array_merge( $classes, $newClasses );
886 $s .=
' ' . $tagSummary;
897 $this->insertTags(
$s, $rc, $classes );
906 return self::isUnpatrolled( $rc, $this->
getUser() );
916 $isPatrolled = $rc->mAttribs[
'rc_patrolled'];
917 $rcType = $rc->mAttribs[
'rc_type'];
918 $rcLogType = $rc->mAttribs[
'rc_log_type'];
920 $isPatrolled = $rc->rc_patrolled;
921 $rcType = $rc->rc_type;
922 $rcLogType = $rc->rc_log_type;
925 if ( !$isPatrolled ) {
950 return intval( $rcObj->getAttribute(
'rc_type' ) ) ===
RC_CATEGORIZE
951 && intval( $rcObj->getAttribute(
'rc_this_oldid' ) ) === 0;
964 case RecentChange::SRC_EDIT:
965 case RecentChange::SRC_NEW:
966 $attrs[
'data-mw-revid'] = $rc->mAttribs[
'rc_this_oldid'];
968 case RecentChange::SRC_LOG:
969 $attrs[
'data-mw-logid'] = $rc->mAttribs[
'rc_logid'];
970 $attrs[
'data-mw-logaction'] =
971 $rc->mAttribs[
'rc_log_type'] .
'/' . $rc->mAttribs[
'rc_log_action'];
973 case RecentChange::SRC_CATEGORIZE:
974 $attrs[
'data-mw-revid'] = $rc->mAttribs[
'rc_this_oldid'];
978 $attrs[
'data-mw-ts' ] = $rc->
getAttribute(
'rc_timestamp' );
991 $this->changeLinePrefixer = $prefixer;
getWatchlistExpiry(WatchedItemStoreInterface $store, Title $title, UserIdentity $user)
Get existing expiry from the database.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Represents a filter group (used on ChangesListSpecialPage and descendants)
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
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=[])
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
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
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
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.
insertLog(&$s, $title, $logtype, $useParentheses=true)
insertTags(&$s, &$rc, &$classes)
ChangesListFilterGroup[] $filterGroups
string[] $formattedComments
Comments indexed by rc_id.
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()
setContext(IContextSource $context)
Marks HTML that shouldn't be escaped.
Base class for language-specific code.
static userLink( $userId, $userName, $altUserName=false)
Make user link (or user contributions for unregistered users)
static getRevisionDeletedClass(RevisionRecord $revisionRecord)
Returns css class of a deleted revision.
static generateRollback(RevisionRecord $revRecord, IContextSource $context=null, $options=[ 'verify'])
Generate a rollback link for a given revision.
static userToolLinks( $userId, $userText, $redContribsWhenNoEdits=false, $flags=0, $edits=null, $useParentheses=true)
Generate standard user tool links (talk, contributions, block link, etc.)
Class to simplify the use of log pages.
Handles a simple LRU key/value map with a maximum number of entries.
A class containing constants representing the names of configuration variables.
Utility class for creating new RC entries.
getAttribute( $name)
Get an attribute value.
useFilePatrol()
Check whether to enable new files patrol features for this user.
useNPPatrol()
Check whether to enable new pages patrol features for this user.
useRCPatrol()
Check whether to enable recent changes patrol features for this user.
Representation of a pair of user and title for watchlist entries.
getExpiryInDaysText(MessageLocalizer $msgLocalizer, $isDropdownOption=false)
Get days remaining until a watched item expires as a text.
Interface for objects which can provide a MediaWiki context on request.
foreach( $mmfl['setupFiles'] as $fileName) if($queue) if(empty( $mmfl['quiet'])) $s
if(!isset( $args[0])) $lang