27 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
42 use ProtectedHookAccessorTrait;
86 $this->preCacheMessages();
90 $services = MediaWikiServices::getInstance();
91 $this->linkRenderer = $services->getLinkRenderer();
92 $this->commentFormatter = $services->getRowCommentFormatter();
106 $services = MediaWikiServices::getInstance();
108 if ( (
new HookRunner( $services->getHookContainer() ) )->onFetchChangesList( $user, $sk, $list, $groups ) ) {
109 $userOptionsLookup = $services->getUserOptionsLookup();
112 $userOptionsLookup->getBoolOption( $user,
'usenewrc' )
135 throw new RuntimeException(
'recentChangesLine should be implemented' );
145 $highlightColorDivs =
'';
146 foreach ( [
'none',
'c1',
'c2',
'c3',
'c4',
'c5' ] as $color ) {
147 $highlightColorDivs .= Html::rawElement(
150 'class' =>
'mw-rcfilters-ui-highlights-color-' . $color,
151 'data-color' => $color
156 return Html::rawElement(
158 [
'class' =>
'mw-rcfilters-ui-highlights' ],
168 $this->watchlist = $value;
183 private function preCacheMessages() {
184 if ( !isset( $this->message ) ) {
187 'cur',
'diff',
'hist',
'enhancedrc-history',
'last',
'blocklink',
'history',
188 'semicolon-separator',
'pipe-separator' ] as $msg
190 $this->message[$msg] = $this->
msg( $msg )->escaped();
204 array_keys( $this->
getConfig()->
get( MainConfigNames::RecentChangesFlags ) ) as $flag
206 $f .= isset( $flags[$flag] ) && $flags[$flag]
223 $classes = [ self::CSS_CLASS_PREFIX .
'line' ];
224 $logType = $rc->mAttribs[
'rc_log_type'];
227 $classes[] = self::CSS_CLASS_PREFIX .
'log';
230 $classes[] = self::CSS_CLASS_PREFIX .
'edit';
232 $rc->mAttribs[
'rc_namespace'] .
'-' . $rc->mAttribs[
'rc_title'] );
237 $classes[] = $watched && $rc->mAttribs[
'rc_timestamp'] >= $watched
238 ? self::CSS_CLASS_PREFIX .
'line-watched'
239 : self::CSS_CLASS_PREFIX .
'line-not-watched';
257 $rc->mAttribs[
'rc_namespace'] );
259 $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
261 self::CSS_CLASS_PREFIX .
263 ( $nsInfo->isTalk( $rc->mAttribs[
'rc_namespace'] ) ?
'talk' :
'subject' )
266 foreach ( $this->filterGroups as $filterGroup ) {
267 foreach ( $filterGroup->getFilters() as $filter ) {
268 $filter->applyCssClassIfNeeded( $this, $rc, $classes );
286 static $map = [
'minoredit' =>
'minor',
'botedit' =>
'bot' ];
287 static $flagInfos =
null;
289 if ( $flagInfos ===
null ) {
290 $recentChangesFlags = MediaWikiServices::getInstance()->getMainConfig()
291 ->get( MainConfigNames::RecentChangesFlags );
293 foreach ( $recentChangesFlags as $key => $value ) {
294 $flagInfos[$key][
'letter'] = $value[
'letter'];
295 $flagInfos[$key][
'title'] = $value[
'title'];
297 $flagInfos[$key][
'class'] = $value[
'class'] ?? $key;
304 if ( isset( $map[$flag] ) ) {
308 $info = $flagInfos[$flag];
309 return Html::element(
'abbr', [
310 'class' => $info[
'class'],
320 $this->rc_cache = [];
321 $this->rcMoveIndex = 0;
322 $this->rcCacheIndex = 0;
323 $this->lastdate =
'';
324 $this->rclistOpen =
false;
326 'mediawiki.interface.helpers.styles',
327 'mediawiki.special.changeslist'
330 return '<div class="mw-changeslist">';
337 $this->getHookRunner()->onChangesListInitRows( $this, $rows );
338 $this->formattedComments = $this->commentFormatter->createBatch()
340 $this->commentFormatter->rows( $rows )
341 ->commentKey(
'rc_comment' )
342 ->namespaceField(
'rc_namespace' )
343 ->titleField(
'rc_title' )
344 ->indexField(
'rc_id' )
367 $szdiff = $new - $old;
369 $lang = $context->getLanguage();
370 $config = $context->getConfig();
371 $code =
$lang->getCode();
372 static $fastCharDiff = [];
373 if ( !isset( $fastCharDiff[$code] ) ) {
374 $fastCharDiff[$code] = $config->get( MainConfigNames::MiserMode )
375 || $context->msg(
'rc-change-size' )->plain() ===
'$1';
378 $formattedSize =
$lang->formatNum( $szdiff );
380 if ( !$fastCharDiff[$code] ) {
381 $formattedSize = $context->msg(
'rc-change-size', $formattedSize )->text();
384 if ( abs( $szdiff ) > abs( $config->get( MainConfigNames::RCChangedSizeThreshold ) ) ) {
390 if ( $szdiff === 0 ) {
391 $formattedSizeClass =
'mw-plusminus-null';
392 } elseif ( $szdiff > 0 ) {
393 $formattedSize =
'+' . $formattedSize;
394 $formattedSizeClass =
'mw-plusminus-pos';
396 $formattedSizeClass =
'mw-plusminus-neg';
398 $formattedSizeClass .=
' mw-diff-bytes';
400 $formattedTotalSize = $context->msg(
'rc-change-size-new' )->numParams( $new )->text();
402 return Html::element( $tag,
403 [
'dir' =>
'ltr',
'class' => $formattedSizeClass,
'title' => $formattedTotalSize ],
404 $formattedSize ) .
$lang->getDirMark();
415 $oldlen = $old->mAttribs[
'rc_old_len'];
418 $newlen = $new->mAttribs[
'rc_new_len'];
420 $newlen = $old->mAttribs[
'rc_new_len'];
423 if ( $oldlen ===
null || $newlen ===
null ) {
435 $out = $this->rclistOpen ?
"</ul>\n" :
'';
460 $date =
$lang->userTimeAndDate( $ts, $performer->
getUser() );
461 if ( $rev->
userCan( RevisionRecord::DELETED_TEXT, $performer ) ) {
462 $link = MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink(
465 [
'class' =>
'mw-changeslist-date' ],
466 [
'oldid' => $rev->
getId() ]
469 $link = htmlspecialchars( $date );
471 if ( $rev->
isDeleted( RevisionRecord::DELETED_TEXT ) ) {
472 $deletedClass = Linker::getRevisionDeletedClass( $rev );
473 $link =
"<span class=\"$deletedClass mw-changeslist-date\">$link</span>";
475 return Html::element(
'span', [
476 'class' =>
'mw-changeslist-time'
485 # Make date header if necessary
487 if ( $date != $this->lastdate ) {
488 if ( $this->lastdate !=
'' ) {
491 $s .=
Xml::element(
'h4',
null, $date ) .
"\n<ul class=\"special\">";
492 $this->lastdate = $date;
493 $this->rclistOpen =
true;
504 $page =
new LogPage( $logtype );
505 $logname = $page->getName()->setContext( $this->
getContext() )->text();
506 $link = $this->linkRenderer->makeKnownLink(
$title, $logname, [
507 'class' => $useParentheses ?
'' :
'mw-changeslist-links'
509 if ( $useParentheses ) {
510 $s .= $this->
msg(
'parentheses' )->rawParams(
526 $rc->mAttribs[
'rc_type'] ==
RC_NEW ||
527 $rc->mAttribs[
'rc_type'] ==
RC_LOG ||
530 $diffLink = $this->message[
'diff'];
531 } elseif ( !self::userCan( $rc, RevisionRecord::DELETED_TEXT, $this->
getAuthority() ) ) {
532 $diffLink = $this->message[
'diff'];
535 'curid' => $rc->mAttribs[
'rc_cur_id'],
536 'diff' => $rc->mAttribs[
'rc_this_oldid'],
537 'oldid' => $rc->mAttribs[
'rc_last_oldid']
540 $diffLink = $this->linkRenderer->makeKnownLink(
543 [
'class' =>
'mw-changeslist-diff' ],
548 $histLink = $this->message[
'hist'];
550 $histLink = $this->linkRenderer->makeKnownLink(
553 [
'class' =>
'mw-changeslist-history' ],
555 'curid' => $rc->mAttribs[
'rc_cur_id'],
556 'action' =>
'history'
561 $s .= Html::rawElement(
'div', [
'class' =>
'mw-changeslist-links' ],
562 Html::rawElement(
'span', [], $diffLink ) .
563 Html::rawElement(
'span', [], $histLink )
565 ' <span class="mw-changeslist-separator"></span> ';
580 if ( $rc->getTitle()->isRedirect() ) {
581 $params = [
'redirect' =>
'no' ];
584 $articlelink = $this->linkRenderer->makeLink(
587 [
'class' =>
'mw-changeslist-title' ],
590 if ( static::isDeleted( $rc, RevisionRecord::DELETED_TEXT ) ) {
591 $class =
'history-deleted';
592 if ( static::isDeleted( $rc, RevisionRecord::DELETED_RESTRICTED ) ) {
593 $class .=
' mw-history-suppressed';
595 $articlelink =
'<span class="' . $class .
'">' . $articlelink .
'</span>';
597 # To allow for boldening pages watched by this user
598 $articlelink =
"<span class=\"mw-title\">{$articlelink}</span>";
600 $articlelink .= $this->
getLanguage()->getDirMark();
602 # TODO: Deprecate the $s argument, it seems happily unused.
604 $this->getHookRunner()->onChangesListInsertArticleLink( $this, $articlelink,
605 $s, $rc, $unpatrolled, $watched );
608 $watchlistExpiry =
'';
609 if ( isset( $rc->watchlistExpiry ) && $rc->watchlistExpiry ) {
613 return "{$s} {$articlelink}{$watchlistExpiry}";
625 if ( $item->isExpired() ) {
630 $widget =
new IconWidget( [
632 'title' => $daysLeftText,
633 'classes' => [
'mw-changesList-watchlistExpiry' ],
635 $widget->setAttributes( [
638 'aria-label' => $this->
msg(
'watchlist-expires-in-aria-label' )->text(),
640 'data-days-left' => $item->getExpiryInDays(),
667 $separatorClass = $rc->watchlistExpiry ?
'mw-changeslist-separator' :
'mw-changeslist-separator--semicolon';
668 return Html::element(
'span', [
'class' => $separatorClass ] ) .
' ' .
669 '<span class="mw-changeslist-date mw-changeslist-time">' .
670 htmlspecialchars( $this->getLanguage()->userTime(
671 $rc->mAttribs[
'rc_timestamp'],
673 ) ) .
'</span> <span class="mw-changeslist-separator"></span> ';
693 if ( static::isDeleted( $rc, RevisionRecord::DELETED_USER ) ) {
694 $deletedClass =
'history-deleted';
695 if ( static::isDeleted( $rc, RevisionRecord::DELETED_RESTRICTED ) ) {
696 $deletedClass .=
' mw-history-suppressed';
698 $s .=
' <span class="' . $deletedClass .
'">' .
699 $this->msg(
'rev-deleted-user' )->escaped() .
'</span>';
701 $s .= $this->getLanguage()->getDirMark() . Linker::userLink(
702 $rc->mAttribs[
'rc_user'],
703 $rc->mAttribs[
'rc_user_text'],
705 [
'data-mw-revid' => $rc->mAttribs[
'rc_this_oldid'] ]
707 $s .= Linker::userToolLinks(
708 $rc->mAttribs[
'rc_user'], $rc->mAttribs[
'rc_user_text'],
725 $formatter->setContext( $this->
getContext() );
726 $formatter->setShowUserToolLinks(
true );
727 $mark = $this->getLanguage()->getDirMark();
729 return Html::openElement(
'span', [
'class' =>
'mw-changeslist-log-entry' ] )
730 . $formatter->getActionText()
732 . $formatter->getComment()
733 . $this->msg(
'word-separator' )->escaped()
734 . $formatter->getActionLinks()
735 . Html::closeElement(
'span' );
744 if ( static::isDeleted( $rc, RevisionRecord::DELETED_COMMENT ) ) {
745 $deletedClass =
'history-deleted';
746 if ( static::isDeleted( $rc, RevisionRecord::DELETED_RESTRICTED ) ) {
747 $deletedClass .=
' mw-history-suppressed';
749 return ' <span class="' . $deletedClass .
' comment">' .
750 $this->msg(
'rev-deleted-comment' )->escaped() .
'</span>';
751 } elseif ( isset( $rc->mAttribs[
'rc_id'] )
752 && isset( $this->formattedComments[$rc->mAttribs[
'rc_id']] )
754 return $this->formattedComments[$rc->mAttribs[
'rc_id']];
756 return $this->commentFormatter->formatBlock(
757 $rc->mAttribs[
'rc_comment'],
779 return $this->watchMsgCache->getWithSetCallback(
780 "watching-users-msg:$count",
781 function () use ( $count ) {
782 return $this->msg(
'number-of-watching-users-for-recent-changes' )
783 ->numParams( $count )->escaped();
795 return ( $rc->mAttribs[
'rc_deleted'] & $field ) == $field;
810 if ( $rc->mAttribs[
'rc_type'] ==
RC_LOG ) {
814 return RevisionRecord::userCanBitfield( $rc->mAttribs[
'rc_deleted'], $field, $performer );
824 return '<strong class="mw-watched">' . $link .
'</strong>';
826 return '<span class="mw-rc-unwatched">' . $link .
'</span>';
837 $this->insertPageTools( $s, $rc );
848 private function insertPageTools( &$s, &$rc ) {
850 if ( !in_array( $rc->mAttribs[
'rc_type'], [
RC_EDIT,
RC_NEW ] )
852 || !$rc->mAttribs[
'rc_this_oldid']
853 || !$rc->mAttribs[
'rc_cur_id']
861 $revRecord->setId( (
int)$rc->mAttribs[
'rc_this_oldid'] );
862 $revRecord->setVisibility( (
int)$rc->mAttribs[
'rc_deleted'] );
864 (
int)$rc->mAttribs[
'rc_user'],
865 $rc->mAttribs[
'rc_user_text']
867 $revRecord->setUser( $user );
873 $rc->getAttribute(
'page_latest' ) == $rc->mAttribs[
'rc_this_oldid']
874 && $rc->mAttribs[
'rc_type'] !=
RC_NEW,
875 $this->getHookRunner(),
879 MediaWikiServices::getInstance()->getLinkRenderer()
882 $s .= $tools->toHTML();
892 $this->insertRollback( $s, $rc );
902 if ( empty( $rc->mAttribs[
'ts_tags'] ) ) {
907 $rc->mAttribs[
'ts_tags'],
911 $classes = array_merge( $classes, $newClasses );
912 $s .=
' ' . $tagSummary;
923 $this->insertTags( $s, $rc, $classes );
932 return self::isUnpatrolled( $rc, $this->
getUser() );
942 $isPatrolled = $rc->mAttribs[
'rc_patrolled'];
943 $rcType = $rc->mAttribs[
'rc_type'];
944 $rcLogType = $rc->mAttribs[
'rc_log_type'];
946 $isPatrolled = $rc->rc_patrolled;
947 $rcType = $rc->rc_type;
948 $rcLogType = $rc->rc_log_type;
951 if ( !$isPatrolled ) {
976 return intval( $rcObj->getAttribute(
'rc_type' ) ) ===
RC_CATEGORIZE
977 && intval( $rcObj->getAttribute(
'rc_this_oldid' ) ) === 0;
992 $attrs[
'data-mw-revid'] = $rc->mAttribs[
'rc_this_oldid'];
995 $attrs[
'data-mw-logid'] = $rc->mAttribs[
'rc_logid'];
996 $attrs[
'data-mw-logaction'] =
997 $rc->mAttribs[
'rc_log_type'] .
'/' . $rc->mAttribs[
'rc_log_action'];
1000 $attrs[
'data-mw-revid'] = $rc->mAttribs[
'rc_this_oldid'];
1004 $attrs[
'data-mw-ts' ] = $rc->
getAttribute(
'rc_timestamp' );
1017 $this->changeLinePrefixer = $prefixer;
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
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()
getContext()
Get the base IContextSource object.
setContext(IContextSource $context)
Marks HTML that shouldn't be escaped.
Base class for language-specific code.
static userCanBitfield( $bitfield, $field, Authority $performer)
Determine if the current user is allowed to view a particular field of this log row,...
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.
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.
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.
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Interface for objects which can provide a MediaWiki context on request.
if(!isset( $args[0])) $lang