25use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
36 use ProtectedHookAccessorTrait;
68 public function __construct( $context, array $filterGroups = [] ) {
72 $this->linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
73 $this->filterGroups = $filterGroups;
88 if ( Hooks::runner()->onFetchChangesList( $user, $sk, $list, $groups ) ) {
89 $new = $context->
getRequest()->getBool(
'enhanced', $user->getOption(
'usenewrc' ) );
111 throw new RuntimeException(
'recentChangesLine should be implemented' );
121 $highlightColorDivs =
'';
122 foreach ( [
'none',
'c1',
'c2',
'c3',
'c4',
'c5' ] as $color ) {
123 $highlightColorDivs .= Html::rawElement(
126 'class' =>
'mw-rcfilters-ui-highlights-color-' . $color,
127 'data-color' => $color
132 return Html::rawElement(
134 [
'class' =>
'mw-rcfilters-ui-highlights' ],
144 $this->watchlist = $value;
152 return (
bool)$this->watchlist;
160 if ( !isset( $this->message ) ) {
163 'cur',
'diff',
'hist',
'enhancedrc-history',
'last',
'blocklink',
'history',
164 'semicolon-separator',
'pipe-separator' ] as $msg
166 $this->message[$msg] = $this->
msg( $msg )->escaped();
179 foreach ( array_keys( $this->
getConfig()->
get(
'RecentChangesFlags' ) ) as $flag ) {
180 $f .= isset( $flags[$flag] ) && $flags[$flag]
197 $classes = [ self::CSS_CLASS_PREFIX .
'line' ];
198 $logType = $rc->mAttribs[
'rc_log_type'];
201 $classes[] = self::CSS_CLASS_PREFIX .
'log';
202 $classes[] = Sanitizer::escapeClass( self::CSS_CLASS_PREFIX .
'log-' . $logType );
204 $classes[] = self::CSS_CLASS_PREFIX .
'edit';
205 $classes[] = Sanitizer::escapeClass( self::CSS_CLASS_PREFIX .
'ns' .
206 $rc->mAttribs[
'rc_namespace'] .
'-' . $rc->mAttribs[
'rc_title'] );
211 $classes[] = $watched && $rc->mAttribs[
'rc_timestamp'] >= $watched
212 ? self::CSS_CLASS_PREFIX .
'line-watched'
213 : self::CSS_CLASS_PREFIX .
'line-not-watched';
230 $classes[] = Sanitizer::escapeClass( self::CSS_CLASS_PREFIX .
'ns-' .
231 $rc->mAttribs[
'rc_namespace'] );
233 $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
234 $classes[] = Sanitizer::escapeClass(
235 self::CSS_CLASS_PREFIX .
237 ( $nsInfo->isTalk( $rc->mAttribs[
'rc_namespace'] ) ?
'talk' :
'subject' )
240 foreach ( $this->filterGroups as $filterGroup ) {
241 foreach ( $filterGroup->getFilters() as $filter ) {
242 $filter->applyCssClassIfNeeded( $this, $rc, $classes );
260 static $map = [
'minoredit' =>
'minor',
'botedit' =>
'bot' ];
261 static $flagInfos =
null;
263 if ( $flagInfos ===
null ) {
267 $flagInfos[$key][
'letter'] = $value[
'letter'];
268 $flagInfos[$key][
'title'] = $value[
'title'];
270 $flagInfos[$key][
'class'] = $value[
'class'] ?? $key;
277 if ( isset( $map[$flag] ) ) {
281 $info = $flagInfos[$flag];
282 return Html::element(
'abbr', [
283 'class' => $info[
'class'],
293 $this->rc_cache = [];
294 $this->rcMoveIndex = 0;
295 $this->rcCacheIndex = 0;
296 $this->lastdate =
'';
297 $this->rclistOpen =
false;
299 'mediawiki.interface.helpers.styles',
300 'mediawiki.special.changeslist'
303 return '<div class="mw-changeslist">';
310 $this->getHookRunner()->onChangesListInitRows( $this, $rows );
325 $context = RequestContext::getMain();
330 $szdiff = $new - $old;
334 $code =
$lang->getCode();
335 static $fastCharDiff = [];
336 if ( !isset( $fastCharDiff[$code] ) ) {
337 $fastCharDiff[$code] = $config->get(
'MiserMode' )
338 ||
$context->
msg(
'rc-change-size' )->plain() ===
'$1';
341 $formattedSize =
$lang->formatNum( $szdiff );
343 if ( !$fastCharDiff[$code] ) {
344 $formattedSize =
$context->
msg(
'rc-change-size', $formattedSize )->text();
347 if ( abs( $szdiff ) > abs( $config->get(
'RCChangedSizeThreshold' ) ) ) {
353 if ( $szdiff === 0 ) {
354 $formattedSizeClass =
'mw-plusminus-null';
355 } elseif ( $szdiff > 0 ) {
356 $formattedSize =
'+' . $formattedSize;
357 $formattedSizeClass =
'mw-plusminus-pos';
359 $formattedSizeClass =
'mw-plusminus-neg';
361 $formattedSizeClass .=
' mw-diff-bytes';
363 $formattedTotalSize =
$context->
msg(
'rc-change-size-new' )->numParams( $new )->text();
365 return Html::element( $tag,
366 [
'dir' =>
'ltr',
'class' => $formattedSizeClass,
'title' => $formattedTotalSize ],
367 $formattedSize ) .
$lang->getDirMark();
378 $oldlen = $old->mAttribs[
'rc_old_len'];
381 $newlen = $new->mAttribs[
'rc_new_len'];
383 $newlen = $old->mAttribs[
'rc_new_len'];
386 if ( $oldlen ===
null || $newlen ===
null ) {
390 return self::showCharacterDifference( $oldlen, $newlen, $this->
getContext() );
398 $out = $this->rclistOpen ?
"</ul>\n" :
'';
422 $date =
$lang->userTimeAndDate( $ts, $performer->
getUser() );
423 if ( $rev->
userCan( RevisionRecord::DELETED_TEXT, $performer ) ) {
424 $link = MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
427 [
'class' =>
'mw-changeslist-date' ],
428 [
'oldid' => $rev->
getId() ]
431 $link = htmlspecialchars( $date );
433 if ( $rev->
isDeleted( RevisionRecord::DELETED_TEXT ) ) {
435 $link =
"<span class=\"$deletedClass mw-changeslist-date\">$link</span>";
445 # Make date header if necessary
447 if ( $date != $this->lastdate ) {
448 if ( $this->lastdate !=
'' ) {
451 $s .= Xml::element(
'h4',
null, $date ) .
"\n<ul class=\"special\">";
452 $this->lastdate = $date;
453 $this->rclistOpen =
true;
464 $page =
new LogPage( $logtype );
465 $logname = $page->getName()->setContext( $this->
getContext() )->text();
466 $link = $this->linkRenderer->makeKnownLink(
$title, $logname, [
467 'class' => $useParentheses ?
'' :
'mw-changeslist-links'
469 if ( $useParentheses ) {
470 $s .= $this->
msg(
'parentheses' )->rawParams(
486 $rc->mAttribs[
'rc_type'] ==
RC_NEW ||
487 $rc->mAttribs[
'rc_type'] ==
RC_LOG ||
490 $diffLink = $this->message[
'diff'];
491 } elseif ( !self::userCan( $rc, RevisionRecord::DELETED_TEXT, $this->
getAuthority() ) ) {
492 $diffLink = $this->message[
'diff'];
495 'curid' => $rc->mAttribs[
'rc_cur_id'],
496 'diff' => $rc->mAttribs[
'rc_this_oldid'],
497 'oldid' => $rc->mAttribs[
'rc_last_oldid']
500 $diffLink = $this->linkRenderer->makeKnownLink(
503 [
'class' =>
'mw-changeslist-diff' ],
508 $histLink = $this->message[
'hist'];
510 $histLink = $this->linkRenderer->makeKnownLink(
513 [
'class' =>
'mw-changeslist-history' ],
515 'curid' => $rc->mAttribs[
'rc_cur_id'],
516 'action' =>
'history'
521 $s .= Html::rawElement(
'div', [
'class' =>
'mw-changeslist-links' ],
522 Html::rawElement(
'span', [], $diffLink ) .
523 Html::rawElement(
'span', [], $histLink )
525 ' <span class="mw-changeslist-separator"></span> ';
540 if ( $rc->getTitle()->isRedirect() ) {
541 $params = [
'redirect' =>
'no' ];
544 $articlelink = $this->linkRenderer->makeLink(
547 [
'class' =>
'mw-changeslist-title' ],
550 if ( $this->
isDeleted( $rc, RevisionRecord::DELETED_TEXT ) ) {
551 $class =
'history-deleted';
552 if ( $this->
isDeleted( $rc, RevisionRecord::DELETED_RESTRICTED ) ) {
553 $class .=
' mw-history-suppressed';
555 $articlelink =
'<span class="' . $class .
'">' . $articlelink .
'</span>';
557 # To allow for boldening pages watched by this user
558 $articlelink =
"<span class=\"mw-title\">{$articlelink}</span>";
560 $articlelink .= $this->
getLanguage()->getDirMark();
562 # TODO: Deprecate the $s argument, it seems happily unused.
564 $this->getHookRunner()->onChangesListInsertArticleLink( $this, $articlelink,
565 $s, $rc, $unpatrolled, $watched );
568 $watchlistExpiry =
'';
569 if ( isset( $rc->watchlistExpiry ) && $rc->watchlistExpiry ) {
573 return "{$s} {$articlelink}{$watchlistExpiry}";
585 if ( $item->isExpired() ) {
590 $widget =
new IconWidget( [
592 'title' => $daysLeftText,
593 'classes' => [
'mw-changesList-watchlistExpiry' ],
595 $widget->setAttributes( [
598 'aria-label' => $this->msg(
'watchlist-expires-in-aria-label' )->text(),
600 'data-days-left' => $item->getExpiryInDays(),
623 $separatorClass = $rc->watchlistExpiry ?
'mw-changeslist-separator' :
'mw-changeslist-separator--semicolon';
624 return Html::element(
'span', [
'class' => $separatorClass ] ) .
' ' .
625 '<span class="mw-changeslist-date">' .
626 htmlspecialchars( $this->getLanguage()->userTime(
627 $rc->mAttribs[
'rc_timestamp'],
629 ) ) .
'</span> <span class="mw-changeslist-separator"></span> ';
649 if ( $this->isDeleted( $rc, RevisionRecord::DELETED_USER ) ) {
650 $deletedClass =
'history-deleted';
651 if ( $this->isDeleted( $rc, RevisionRecord::DELETED_RESTRICTED ) ) {
652 $deletedClass =
' mw-history-suppressed';
654 $s .=
' <span class="' . $deletedClass .
'">' .
655 $this->msg(
'rev-deleted-user' )->escaped() .
'</span>';
657 $s .= $this->getLanguage()->getDirMark() .
Linker::userLink( $rc->mAttribs[
'rc_user'],
658 $rc->mAttribs[
'rc_user_text'] );
660 $rc->mAttribs[
'rc_user'], $rc->mAttribs[
'rc_user_text'],
677 $formatter->setContext( $this->
getContext() );
678 $formatter->setShowUserToolLinks(
true );
679 $mark = $this->getLanguage()->getDirMark();
681 return Html::openElement(
'span', [
'class' =>
'mw-changeslist-log-entry' ] )
682 . $formatter->getActionText() .
" $mark" . $formatter->getComment()
683 . Html::closeElement(
'span' );
692 if ( $this->isDeleted( $rc, RevisionRecord::DELETED_COMMENT ) ) {
693 $deletedClass =
'history-deleted';
694 if ( $this->isDeleted( $rc, RevisionRecord::DELETED_RESTRICTED ) ) {
695 $deletedClass .=
' mw-history-suppressed';
697 return ' <span class="' . $deletedClass .
' comment">' .
698 $this->msg(
'rev-deleted-comment' )->escaped() .
'</span>';
720 return $this->watchMsgCache->getWithSetCallback(
721 "watching-users-msg:$count",
722 function () use ( $count ) {
723 return $this->msg(
'number-of-watching-users-for-recent-changes' )
724 ->numParams( $count )->escaped();
736 return ( $rc->mAttribs[
'rc_deleted'] & $field ) == $field;
749 if ( $performer ===
null ) {
750 $performer = RequestContext::getMain()->getAuthority();
753 if ( $rc->mAttribs[
'rc_type'] ==
RC_LOG ) {
754 return LogEventsList::userCanBitfield( $rc->mAttribs[
'rc_deleted'], $field, $performer );
757 return RevisionRecord::userCanBitfield( $rc->mAttribs[
'rc_deleted'], $field, $performer );
767 return '<strong class="mw-watched">' . $link .
'</strong>';
769 return '<span class="mw-rc-unwatched">' . $link .
'</span>';
780 if ( $rc->mAttribs[
'rc_type'] ==
RC_EDIT
781 && $rc->mAttribs[
'rc_this_oldid']
782 && $rc->mAttribs[
'rc_cur_id']
783 && $rc->getAttribute(
'page_latest' ) == $rc->mAttribs[
'rc_this_oldid']
791 $revRecord->setId( (
int)$rc->mAttribs[
'rc_this_oldid'] );
792 $revRecord->setVisibility( (
int)$rc->mAttribs[
'rc_deleted'] );
794 (
int)$rc->mAttribs[
'rc_user'],
795 $rc->mAttribs[
'rc_user_text']
797 $revRecord->setUser( $user );
816 $this->insertRollback(
$s, $rc );
826 if ( empty( $rc->mAttribs[
'ts_tags'] ) ) {
831 $rc->mAttribs[
'ts_tags'],
835 $classes = array_merge( $classes, $newClasses );
836 $s .=
' ' . $tagSummary;
847 $this->insertTags(
$s, $rc, $classes );
856 return self::isUnpatrolled( $rc, $this->getUser() );
866 $isPatrolled = $rc->mAttribs[
'rc_patrolled'];
867 $rcType = $rc->mAttribs[
'rc_type'];
868 $rcLogType = $rc->mAttribs[
'rc_log_type'];
870 $isPatrolled = $rc->rc_patrolled;
871 $rcType = $rc->rc_type;
872 $rcLogType = $rc->rc_log_type;
875 if ( !$isPatrolled ) {
900 return intval( $rcObj->getAttribute(
'rc_type' ) ) ===
RC_CATEGORIZE
901 && intval( $rcObj->getAttribute(
'rc_this_oldid' ) ) === 0;
914 case RecentChange::SRC_EDIT:
915 case RecentChange::SRC_NEW:
916 $attrs[
'data-mw-revid'] = $rc->mAttribs[
'rc_this_oldid'];
918 case RecentChange::SRC_LOG:
919 $attrs[
'data-mw-logid'] = $rc->mAttribs[
'rc_logid'];
920 $attrs[
'data-mw-logaction'] =
921 $rc->mAttribs[
'rc_log_type'] .
'/' . $rc->mAttribs[
'rc_log_action'];
925 $attrs[
'data-mw-ts' ] = $rc->
getAttribute(
'rc_timestamp' );
938 $this->changeLinePrefixer = $prefixer;
getWatchlistExpiry(WatchedItemStoreInterface $store, Title $title, UserIdentity $user)
Get existing expiry from the database.
$wgRecentChangesFlags
Flags (letter symbols) shown in recent changes and watchlist to indicate certain types of edits.
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)
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
preCacheMessages()
As we use the same small set of messages in various methods and that they are called often,...
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
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.
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
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 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...
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.
Utility class for creating new RC entries.
getAttribute( $name)
Get an attribute value.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
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.
getConfig()
Get the site configuration.
msg( $key,... $params)
This is the method for getting translated interface messages.
foreach( $mmfl['setupFiles'] as $fileName) if($queue) if(empty( $mmfl['quiet'])) $s
if(!isset( $args[0])) $lang