getOutput()->addModules( 'ext.cleanchanges.uls' ); } if ( $wgCCFiltersOnly ) { return true; } /* allow override */ $request = $skin->getRequest(); if ( $request->getBool( 'cleanrc' ) ) { $list = new NCL( $skin ); } if ( $request->getBool( 'newrc' ) ) { $list = new EnhancedChangesList( $skin ); } if ( $request->getBool( 'oldrc' ) ) { $list = new OldChangesList( $skin ); } if ( !$list && $user->getOption( 'usenewrc' ) ) { $list = new NCL( $skin ); } if ( $list instanceof NCL ) { $skin->getOutput()->addModules( 'ext.cleanchanges' ); } /* If some list was specified, stop processing */ return $list === null; } protected static $userinfo = []; /** * @param array &$vars * @return bool */ public static function addScriptVariables( &$vars ) { $vars += self::$userinfo; return true; } /** * String that comes between page details and the user details. By default * only larger space. */ protected $userSeparator = "\xc2\xa0 \xc2\xa0"; /** * Text direction, true for ltr and false for rtl */ protected $direction = true; /** * Text direction mark (LRM or RLM) * @var string */ protected $dir; /** * @param IContextSource|Skin $skin */ public function __construct( $skin ) { $lang = $this->getLanguage(); parent::__construct( $skin ); $this->direction = !$lang->isRTL(); $this->dir = $lang->getDirMark(); } /** * @return String */ public function beginRecentChangesList() { parent::beginRecentChangesList(); $dir = $this->direction ? 'ltr' : 'rtl'; return Xml::openElement( 'div', [ 'style' => "direction: $dir" ] ); } /** * @return string */ public function endRecentChangesList() { return $this->recentChangesBlock() . ''; } /** * @param RCCacheEntry|null $rc * @return int */ protected function isLog( RCCacheEntry $rc = null ) { if ( $rc && $rc->getAttribute( 'rc_type' ) == RC_LOG ) { return 2; } return 0; } /** * @param RCCacheEntry $rc * @return string */ protected function getLogTitle( RCCacheEntry $rc ) { $logtype = $rc->getAttribute( 'rc_log_type' ); $logpage = new LogPage( $logtype ); $logname = $logpage->getName()->escaped(); $titleObj = SpecialPage::getTitleFor( 'Log', $logtype ); $link = Linker::link( $titleObj, $logname ); return $this->msg( 'parentheses' )->rawParams( $link )->escaped(); } /** * Format a line for enhanced recentchange (aka with JavaScript and block of lines). * @param RecentChange &$baseRC * @param bool $watched * @param int|null $linenumber * @return string */ public function recentChangesLine( &$baseRC, $watched = false, $linenumber = null ) { # Create a specialised object $rc = RCCacheEntry::newFromParent( $baseRC ); // Extract most used variables $timestamp = $rc->getAttribute( 'rc_timestamp' ); $titleObj = $rc->getTitle(); $rc_id = $rc->getAttribute( 'rc_id' ); $lang = $this->getLanguage(); $date = $lang->date( $timestamp, /* adj */ true, /* format */ true ); $time = $lang->time( $timestamp, /* adj */ true, /* format */ true ); # Should patrol-related stuff be shown? $rc->unpatrolled = $this->showAsUnpatrolled( $rc ); $logEntry = $this->isLog( $rc ); if ( $logEntry ) { $clink = $this->getLogTitle( $rc ); } elseif ( $rc->unpatrolled && $rc->getAttribute( 'rc_type' ) == RC_NEW ) { # Unpatrolled new page, give rc_id in query $clink = linker::linkKnown( $titleObj, null, [], [ 'rcid' => $rc_id ] ); } else { $clink = Linker::linkKnown( $titleObj ); } $rc->watched = $watched; $rc->link = $this->maybeWatchedLink( $clink, $watched ); $rc->timestamp = $time; $rc->numberofWatchingusers = $baseRC->numberofWatchingusers; $rc->_reqCurId = [ 'curid' => $rc->getAttribute( 'rc_cur_id' ) ]; $rc->_reqOldId = [ 'oldid' => $rc->getAttribute( 'rc_this_oldid' ) ]; $this->makeLinks( $rc ); // Make user links if ( self::isDeleted( $rc, Revision::DELETED_USER ) ) { $rc->_user = ' ' . $this->msg( 'rev-deleted-user' )->escaped() . ''; $rc->_userInfo = ''; self::$userinfo += []; } else { // @phan-suppress-next-line SecurityCheck-DoubleEscaped $rc->_user = Linker::userLink( $rc->getAttribute( 'rc_user' ), $rc->getAttribute( 'rc_user_text' ) ); $stuff = $this->userToolLinks( $rc->getAttribute( 'rc_user' ), $rc->getAttribute( 'rc_user_text' ) ); // TODO: userToolLinks can return '' self::$userinfo += $stuff[1]; $rc->_userInfo = $stuff[0]; } if ( !$this->isLog( $rc ) ) { $rc->_comment = $this->getComment( $rc ); } $rc->_watching = $this->numberofWatchingusers( $baseRC->numberofWatchingusers ); # If it's a new day, add the headline and flush the cache $ret = ''; if ( $date !== $this->lastdate ) { # Process current cache $ret = $this->recentChangesBlock(); $this->rc_cache = []; $ret .= Xml::element( 'h4', null, $date ) . "\n"; $this->lastdate = $date; } # Put accumulated information into the cache, for later display # Page moves go on their own line if ( $logEntry ) { $secureName = $this->getLogTitle( $rc ); } else { $secureName = $titleObj->getPrefixedDBkey(); } $this->rc_cache[$secureName][] = $rc; return $ret; } /** * @param RCCacheEntry $rc */ protected function makeLinks( RCCacheEntry $rc ) { /* These will be overriden with actual links below, if applicable */ $rc->_curLink = $this->message['cur']; $rc->_diffLink = $this->message['diff']; $rc->_lastLink = $this->message['last']; $rc->_histLink = $this->message['hist']; if ( !$this->isLog( $rc ) ) { # Make cur, diff and last links $querycur = [ 'diff' => 0 ] + $rc->_reqCurId + $rc->_reqOldId; $querydiff = [ 'diff' => $rc->getAttribute( 'rc_this_oldid' ), 'oldid' => $rc->getAttribute( 'rc_last_oldid' ), 'rcid' => $rc->unpatrolled ? $rc->getAttribute( 'rc_id' ) : '', ] + $rc->_reqCurId; $rc->_curLink = Linker::linkKnown( $rc->getTitle(), $this->message['cur'], [], $querycur ); if ( $rc->getAttribute( 'rc_type' ) != RC_NEW ) { $rc->_diffLink = Linker::linkKnown( $rc->getTitle(), $this->message['diff'], [], $querydiff ); } if ( $rc->getAttribute( 'rc_last_oldid' ) != 0 ) { // This is not the first revision $rc->_lastLink = Linker::linkKnown( $rc->getTitle(), $this->message['last'], [], $querydiff ); } $rc->_histLink = Linker::link( $rc->getTitle(), $this->message['hist'], [], $rc->_reqCurId + [ 'action' => 'history' ] ); } } /** * Enhanced RC group * @param RCCacheEntry[] $block * @return string */ protected function recentChangesBlockGroup( $block ) { # Collate list of users $isnew = false; $userlinks = []; $overrides = [ 'minor' => false, 'bot' => false ]; $oldid = 0; foreach ( $block as $rcObj ) { $oldid = $rcObj->getAttribute( 'rc_last_oldid' ); if ( $rcObj->getAttribute( 'rc_new' ) ) { $isnew = $overrides['new'] = true; } $u = $rcObj->_user; if ( !isset( $userlinks[$u] ) ) { $userlinks[$u] = 0; } if ( $rcObj->unpatrolled ) { $overrides['patrol'] = true; } $userlinks[$u]++; } # Main line, flags and timestamp $info = Xml::tags( 'code', null, $this->getFlags( $block[0], $overrides ) . ' ' . $block[0]->timestamp ); $rci = 'RCI' . $this->rcCacheIndex; $rcl = 'RCL' . $this->rcCacheIndex; $rcm = 'RCM' . $this->rcCacheIndex; $linkAttribs = [ 'data-mw-cleanchanges-level' => $rci, 'data-mw-cleanchanges-other' => $rcm, 'data-mw-cleanchanges-link' => $rcl, 'tabindex' => '0', 'role' => 'button', 'class' => 'mw-cleanchanges-showblock' ]; $tl = Xml::tags( 'span', [ 'id' => $rcm ], Xml::tags( 'a', $linkAttribs, $this->arrow( $this->direction ? 'r' : 'l' ) ) ) . Xml::tags( 'span', [ 'id' => $rcl, 'style' => 'display: none;' ], Xml::tags( 'a', $linkAttribs, $this->downArrow() ) ); $items = [ $tl . $info ]; # Article link $items[] = $block[0]->link; $log = $this->isLog( $block[0] ); if ( !$log ) { # Changes $n = count( $block ); static $nchanges = []; if ( !isset( $nchanges[$n] ) ) { $nchanges[$n] = $this->msg( 'nchanges' )->numParams( $n )->escaped(); } if ( !$isnew ) { $changes = Linker::linkKnown( $block[0]->getTitle(), $nchanges[$n], [], [ 'curid' => $block[0]->getAttribute( 'rc_cur_id' ), 'diff' => $block[0]->getAttribute( 'rc_this_oldid' ), 'oldid' => $oldid ] ); } else { $changes = $nchanges[$n]; } $size = $this->getCharacterDifference( $block[0], $block[count( $block ) - 1] ); $items[] = $this->changeInfo( $changes, $block[0]->_histLink, $size ); } $items[] = $this->userSeparator; # Sort the list and convert to text $items[] = $this->makeUserlinks( $userlinks ); $items[] = $block[0]->_watching; $lines = Xml::tags( 'div', null, implode( " {$this->dir}", $items ) ) . "\n"; # Sub-entries $lines .= Xml::tags( 'div', [ 'id' => $rci, 'style' => 'display: none;' ], $this->subEntries( $block ) ) . "\n"; $this->rcCacheIndex++; return $lines . "\n"; } /** * Generate HTML for an arrow or placeholder graphic * @param string $dir One of '', 'd', 'l', 'r' * @param string $alt * @param string $title * @return string HTML "" tag */ protected function arrow( $dir, $alt = '', $title = '' ) { global $wgExtensionAssetsPath; return Html::element( 'img', [ 'src' => "$wgExtensionAssetsPath/CleanChanges/images/Arr_$dir.png", 'width' => 12, 'height' => 12, 'alt' => $alt, 'title' => $title, ] ); } /** * Generate HTML for a right- or left-facing arrow, * depending on language direction. * @return string HTML "" tag */ protected function sideArrow() { $dir = $this->getLanguage()->isRTL() ? 'l' : 'r'; return $this->arrow( $dir, '+', $this->msg( 'rc-enhanced-expand' )->text() ); } /** * Generate HTML for a down-facing arrow * depending on language direction. * @return string HTML "" tag */ protected function downArrow() { return $this->arrow( 'd', '-', $this->msg( 'rc-enhanced-hide' )->text() ); } /** * Generate HTML for a spacer image * @return string HTML "" tag */ protected function spacerArrow() { return $this->arrow( '', UtfNormal\Utils::codepointToUtf8( 0xa0 ) ); // non-breaking space } /** * @param RCCacheEntry[] $block * @return string */ protected function subEntries( array $block ) { $lines = ''; foreach ( $block as $rcObj ) { $items = []; $log = $this->isLog( $rcObj ); $time = $rcObj->timestamp; if ( !$log ) { $time = Linker::linkKnown( $rcObj->getTitle(), htmlspecialchars( $rcObj->timestamp ), [], /** @phan-suppress-next-line PhanTypeMismatchArgument */ $rcObj->_reqOldId + $rcObj->_reqCurId ); } $info = $this->getFlags( $rcObj ) . ' ' . $time; $items[] = $this->spacerArrow() . Xml::tags( 'code', null, $info ); if ( !$log ) { $cur = $rcObj->_curLink; $last = $rcObj->_lastLink; if ( $block[0] === $rcObj ) { // no point diffing first to first $cur = $this->message['cur']; } $items[] = $this->changeInfo( $cur, $last, $this->getCharacterDifference( $rcObj ) ); } $items[] = $this->userSeparator; if ( $this->isLog( $rcObj ) ) { $items[] = $this->insertLogEntry( $rcObj ); } else { $items[] = $rcObj->_user; $items[] = $rcObj->_userInfo; $items[] = $rcObj->_comment; } $lines .= '