setContext( $obj ); $this->skin = $obj->getSkin(); } else { $this->setContext( $obj->getContext() ); $this->skin = $obj; } $this->preCacheMessages(); $this->watchMsgCache = new MapCacheLRU( 50 ); $this->linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer(); $this->filterGroups = $filterGroups; } /** * Fetch an appropriate changes list class for the specified context * Some users might want to use an enhanced list format, for instance * * @param IContextSource $context * @param array $groups Array of ChangesListFilterGroup objects (currently optional) * @return ChangesList */ public static function newFromContext( IContextSource $context, array $groups = [] ) { $user = $context->getUser(); $sk = $context->getSkin(); $list = null; if ( Hooks::run( 'FetchChangesList', [ $user, &$sk, &$list, $groups ] ) ) { $new = $context->getRequest()->getBool( 'enhanced', $user->getOption( 'usenewrc' ) ); return $new ? new EnhancedChangesList( $context, $groups ) : new OldChangesList( $context, $groups ); } else { return $list; } } /** * Format a line * * @since 1.27 * * @param RecentChange &$rc Passed by reference * @param bool $watched (default false) * @param int|null $linenumber (default null) * * @return string|bool */ public function recentChangesLine( &$rc, $watched = false, $linenumber = null ) { throw new RuntimeException( 'recentChangesLine should be implemented' ); } /** * Get the container for highlights that are used in the new StructuredFilters * system * * @return string HTML structure of the highlight container div */ protected function getHighlightsContainerDiv() { $highlightColorDivs = ''; foreach ( [ 'none', 'c1', 'c2', 'c3', 'c4', 'c5' ] as $color ) { $highlightColorDivs .= Html::rawElement( 'div', [ 'class' => 'mw-rcfilters-ui-highlights-color-' . $color, 'data-color' => $color ] ); } return Html::rawElement( 'div', [ 'class' => 'mw-rcfilters-ui-highlights' ], $highlightColorDivs ); } /** * Sets the list to use a "
  • " tag * @param bool $value */ public function setWatchlistDivs( $value = true ) { $this->watchlist = $value; } /** * @return bool True when setWatchlistDivs has been called * @since 1.23 */ public function isWatchlist() { return (bool)$this->watchlist; } /** * As we use the same small set of messages in various methods and that * they are called often, we call them once and save them in $this->message */ private function preCacheMessages() { if ( !isset( $this->message ) ) { $this->message = []; foreach ( [ 'cur', 'diff', 'hist', 'enhancedrc-history', 'last', 'blocklink', 'history', 'semicolon-separator', 'pipe-separator' ] as $msg ) { $this->message[$msg] = $this->msg( $msg )->escaped(); } } } /** * Returns the appropriate flags for new page, minor change and patrolling * @param array $flags Associative array of 'flag' => Bool * @param string $nothing To use for empty space * @return string */ public function recentChangesFlags( $flags, $nothing = "\u{00A0}" ) { $f = ''; foreach ( array_keys( $this->getConfig()->get( 'RecentChangesFlags' ) ) as $flag ) { $f .= isset( $flags[$flag] ) && $flags[$flag] ? self::flag( $flag, $this->getContext() ) : $nothing; } return $f; } /** * Get an array of default HTML class attributes for the change. * * @param RecentChange|RCCacheEntry $rc * @param string|bool $watched Optionally timestamp for adding watched class * * @return string[] List of CSS class names */ protected function getHTMLClasses( $rc, $watched ) { $classes = [ self::CSS_CLASS_PREFIX . 'line' ]; $logType = $rc->mAttribs['rc_log_type']; if ( $logType ) { $classes[] = self::CSS_CLASS_PREFIX . 'log'; $classes[] = Sanitizer::escapeClass( self::CSS_CLASS_PREFIX . 'log-' . $logType ); } else { $classes[] = self::CSS_CLASS_PREFIX . 'edit'; $classes[] = Sanitizer::escapeClass( self::CSS_CLASS_PREFIX . 'ns' . $rc->mAttribs['rc_namespace'] . '-' . $rc->mAttribs['rc_title'] ); } // Indicate watched status on the line to allow for more // comprehensive styling. $classes[] = $watched && $rc->mAttribs['rc_timestamp'] >= $watched ? self::CSS_CLASS_PREFIX . 'line-watched' : self::CSS_CLASS_PREFIX . 'line-not-watched'; $classes = array_merge( $classes, $this->getHTMLClassesForFilters( $rc ) ); return $classes; } /** * Get an array of CSS classes attributed to filters for this row. Used for highlighting * in the front-end. * * @param RecentChange $rc * @return array Array of CSS classes */ protected function getHTMLClassesForFilters( $rc ) { $classes = []; $classes[] = Sanitizer::escapeClass( self::CSS_CLASS_PREFIX . 'ns-' . $rc->mAttribs['rc_namespace'] ); $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo(); $classes[] = Sanitizer::escapeClass( self::CSS_CLASS_PREFIX . 'ns-' . ( $nsInfo->isTalk( $rc->mAttribs['rc_namespace'] ) ? 'talk' : 'subject' ) ); if ( $this->filterGroups !== null ) { foreach ( $this->filterGroups as $filterGroup ) { foreach ( $filterGroup->getFilters() as $filter ) { $filter->applyCssClassIfNeeded( $this, $rc, $classes ); } } } return $classes; } /** * Make an "" element for a given change flag. The flag indicating a new page, minor edit, * bot edit, or unpatrolled edit. In English it typically contains "N", "m", "b", or "!". * * @param string $flag One key of $wgRecentChangesFlags * @param IContextSource|null $context * @return string HTML */ public static function flag( $flag, IContextSource $context = null ) { static $map = [ 'minoredit' => 'minor', 'botedit' => 'bot' ]; static $flagInfos = null; if ( is_null( $flagInfos ) ) { global $wgRecentChangesFlags; $flagInfos = []; foreach ( $wgRecentChangesFlags as $key => $value ) { $flagInfos[$key]['letter'] = $value['letter']; $flagInfos[$key]['title'] = $value['title']; // Allow customized class name, fall back to flag name $flagInfos[$key]['class'] = $value['class'] ?? $key; } } $context = $context ?: RequestContext::getMain(); // Inconsistent naming, kepted for b/c if ( isset( $map[$flag] ) ) { $flag = $map[$flag]; } $info = $flagInfos[$flag]; return Html::element( 'abbr', [ 'class' => $info['class'], 'title' => wfMessage( $info['title'] )->setContext( $context )->text(), ], wfMessage( $info['letter'] )->setContext( $context )->text() ); } /** * Returns text for the start of the tabular part of RC * @return string */ public function beginRecentChangesList() { $this->rc_cache = []; $this->rcMoveIndex = 0; $this->rcCacheIndex = 0; $this->lastdate = ''; $this->rclistOpen = false; $this->getOutput()->addModuleStyles( [ 'mediawiki.interface.helpers.styles', 'mediawiki.special.changeslist' ] ); return '
    '; } /** * @param IResultWrapper|array $rows */ public function initChangesListRows( $rows ) { Hooks::run( 'ChangesListInitRows', [ $this, $rows ] ); } /** * Show formatted char difference * * Needs the css module 'mediawiki.special.changeslist' to style output * * @param int $old Number of bytes * @param int $new Number of bytes * @param IContextSource|null $context * @return string */ public static function showCharacterDifference( $old, $new, IContextSource $context = null ) { if ( !$context ) { $context = RequestContext::getMain(); } $new = (int)$new; $old = (int)$old; $szdiff = $new - $old; $lang = $context->getLanguage(); $config = $context->getConfig(); $code = $lang->getCode(); static $fastCharDiff = []; if ( !isset( $fastCharDiff[$code] ) ) { $fastCharDiff[$code] = $config->get( 'MiserMode' ) || $context->msg( 'rc-change-size' )->plain() === '$1'; } $formattedSize = $lang->formatNum( $szdiff ); if ( !$fastCharDiff[$code] ) { $formattedSize = $context->msg( 'rc-change-size', $formattedSize )->text(); } if ( abs( $szdiff ) > abs( $config->get( 'RCChangedSizeThreshold' ) ) ) { $tag = 'strong'; } else { $tag = 'span'; } if ( $szdiff === 0 ) { $formattedSizeClass = 'mw-plusminus-null'; } elseif ( $szdiff > 0 ) { $formattedSize = '+' . $formattedSize; $formattedSizeClass = 'mw-plusminus-pos'; } else { $formattedSizeClass = 'mw-plusminus-neg'; } $formattedSizeClass .= ' mw-diff-bytes'; $formattedTotalSize = $context->msg( 'rc-change-size-new' )->numParams( $new )->text(); return Html::element( $tag, [ 'dir' => 'ltr', 'class' => $formattedSizeClass, 'title' => $formattedTotalSize ], $formattedSize ) . $lang->getDirMark(); } /** * Format the character difference of one or several changes. * * @param RecentChange $old * @param RecentChange|null $new Last change to use, if not provided, $old will be used * @return string HTML fragment */ public function formatCharacterDifference( RecentChange $old, RecentChange $new = null ) { $oldlen = $old->mAttribs['rc_old_len']; if ( $new ) { $newlen = $new->mAttribs['rc_new_len']; } else { $newlen = $old->mAttribs['rc_new_len']; } if ( $oldlen === null || $newlen === null ) { return ''; } return self::showCharacterDifference( $oldlen, $newlen, $this->getContext() ); } /** * Returns text for the end of RC * @return string */ public function endRecentChangesList() { $out = $this->rclistOpen ? "\n" : ''; $out .= '
    '; return $out; } /** * Render the date and time of a revision in the current user language * based on whether the user is able to view this information or not. * @param Revision $rev * @param User $user * @param Language $lang * @param Title|null $title (optional) where Title does not match * the Title associated with the Revision * @internal For usage by Pager classes only (e.g. HistoryPager and ContribsPager). * @return string HTML */ public static function revDateLink( Revision $rev, User $user, Language $lang, $title = null ) { $ts = $rev->getTimestamp(); $date = $lang->userTimeAndDate( $ts, $user ); if ( $rev->userCan( RevisionRecord::DELETED_TEXT, $user ) ) { $link = MediaWikiServices::getInstance()->getLinkRenderer()->makeKnownLink( $title ?? $rev->getTitle(), $date, [ 'class' => 'mw-changeslist-date' ], [ 'oldid' => $rev->getId() ] ); } else { $link = htmlspecialchars( $date ); } if ( $rev->isDeleted( RevisionRecord::DELETED_TEXT ) ) { $link = "$link"; } return $link; } /** * @param string &$s HTML to update * @param mixed $rc_timestamp */ public function insertDateHeader( &$s, $rc_timestamp ) { # Make date header if necessary $date = $this->getLanguage()->userDate( $rc_timestamp, $this->getUser() ); if ( $date != $this->lastdate ) { if ( $this->lastdate != '' ) { $s .= "\n"; } $s .= Xml::element( 'h4', null, $date ) . "\n