summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'MLEB/CleanChanges/includes/NCL.php')
-rw-r--r--MLEB/CleanChanges/includes/NCL.php756
1 files changed, 756 insertions, 0 deletions
diff --git a/MLEB/CleanChanges/includes/NCL.php b/MLEB/CleanChanges/includes/NCL.php
new file mode 100644
index 00000000..1fa5c014
--- /dev/null
+++ b/MLEB/CleanChanges/includes/NCL.php
@@ -0,0 +1,756 @@
+<?php
+
+/**
+ * Generate a list of changes using an Enhanced system (use javascript).
+ */
+class NCL extends EnhancedChangesList {
+ /**
+ * Determines which version of changes list to provide, or none.
+ * @param User $user
+ * @param Skin $skin
+ * @param array &$list
+ * @return bool
+ */
+ public static function hook( User $user, Skin $skin, &$list ) {
+ global $wgCCTrailerFilter, $wgCCFiltersOnly;
+
+ $list = null;
+
+ if ( $wgCCTrailerFilter && defined( 'ULS_VERSION' ) ) {
+ $skin->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() . '</div>';
+ }
+
+ /**
+ * @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 = ' <span class="history-deleted">' .
+ $this->msg( 'rev-deleted-user' )->escaped() .
+ '</span>';
+ $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 "<img>" 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 "<img>" 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 "<img>" tag
+ */
+ protected function downArrow() {
+ return $this->arrow( 'd', '-', $this->msg( 'rc-enhanced-hide' )->text() );
+ }
+
+ /**
+ * Generate HTML for a spacer image
+ * @return string HTML "<img>" 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 .= '<div>' . implode( " {$this->dir}", $items ) . "</div>\n";
+ }
+ return $lines;
+ }
+
+ /**
+ * @param string $diff
+ * @param string $hist
+ * @param mixed $size
+ * @return string
+ */
+ protected function changeInfo( $diff, $hist, $size ) {
+ if ( is_int( $size ) ) {
+ $size = $this->wrapCharacterDifference( $size );
+ // FIXME: i18n: Hard coded parentheses and spaces.
+ return $this->msg( 'cleanchanges-rcinfo-3' )->rawParams( $diff, $hist, $size )->escaped();
+ } else {
+ return $this->msg( 'cleanchanges-rcinfo-2' )->rawParams( $diff, $hist )->escaped();
+ }
+ }
+
+ /**
+ * Enhanced RC ungrouped line.
+ * @param RCCacheEntry $rcObj
+ * @return string a HTML formated line
+ */
+ protected function recentChangesBlockLine( $rcObj ) {
+ # Flag and Timestamp
+ $info = $this->getFlags( $rcObj ) . ' ' . $rcObj->timestamp;
+ $items = [ $this->spacerArrow() . Xml::tags( 'code', null, $info ) ];
+
+ # Article link
+ $items[] = $rcObj->link;
+
+ if ( !$this->isLog( $rcObj ) ) {
+ $items[] = $this->changeInfo( $rcObj->_diffLink, $rcObj->_histLink,
+ $this->getCharacterDifference( $rcObj )
+ );
+ }
+
+ $items[] = $this->userSeparator;
+
+ if ( $this->isLog( $rcObj ) ) {
+ $items[] = $this->insertLogEntry( $rcObj );
+ } else {
+ $items[] = $rcObj->_user;
+ $items[] = $rcObj->_userInfo;
+ $items[] = $rcObj->_comment;
+ $items[] = $rcObj->_watching;
+ }
+
+ return '<div>' . implode( " {$this->dir}", $items ) . "</div>\n";
+ }
+
+ /**
+ * @param RCCacheEntry $rc
+ * @return string
+ */
+ public function getComment( RCCacheEntry $rc ) {
+ $comment = $rc->getAttribute( 'rc_comment' );
+ $action = '';
+ if ( $comment === '' ) {
+ return $action;
+ } elseif ( self::isDeleted( $rc, LogPage::DELETED_COMMENT ) ) {
+ $priviledged = $this->getUser()->isAllowed( 'deleterevision' );
+ if ( $priviledged ) {
+ return $action . ' <span class="history-deleted">' .
+ Linker::formatComment( $comment ) .
+ '</span>';
+ }
+ return $action . ' <span class="history-deleted">' .
+ $this->msg( 'rev-deleted-comment' )->escaped() .
+ '</span>';
+ }
+ return $action . Linker::commentBlock( $comment, $rc->getTitle() );
+ }
+
+ /**
+ * Enhanced user tool links, with javascript functionality.
+ * @param int $userId user id, 0 for anons
+ * @param string $userText username
+ * @return array|string Either an array of html and array of messages, or ''
+ * [0]: html span and links to user tools
+ * [1]: array of escaped message strings
+ */
+ public function userToolLinks( $userId, $userText ) {
+ global $wgDisableAnonTalk;
+ $talkable = !( $wgDisableAnonTalk && 0 == $userId );
+
+ /*
+ * Assign each different user a running id. This is used to show user tool
+ * links on demand with javascript, to reduce page size when one user has
+ * multiple changes.
+ *
+ * $linkindex is the running id, and $users contain username -> html snippet
+ * for javascript.
+ */
+
+ static $linkindex = 0;
+ $linkindex++;
+
+ static $users = [];
+ $userindex = array_search( $userText, $users, true );
+ if ( $userindex === false ) {
+ $users[] = $userText;
+ $userindex = count( $users ) - 1;
+ }
+
+ global $wgExtensionAssetsPath;
+ $image = Xml::element( 'img', [
+ 'src' => $wgExtensionAssetsPath . '/CleanChanges/images/showuserlinks.png',
+ 'alt' => $this->msg( 'cleanchanges-showuserlinks' )->text(),
+ 'title' => $this->msg( 'cleanchanges-showuserlinks' )->text(),
+ 'width' => '15',
+ 'height' => '11',
+ ]
+ );
+
+ $rci = 'RCUI' . $userindex;
+ $rcl = 'RCUL' . $linkindex;
+ $rcm = 'RCUM' . $linkindex;
+ $linkAttribs = [
+ 'tabindex' => '0',
+ 'role' => 'button',
+ 'class' => 'mw-cleanchanges-showuserinfo',
+ 'data-mw-userinfo-id' => $rci,
+ 'data-mw-userinfo-target' => $rcl
+ ];
+ $tl = Xml::tags( 'span', [ 'id' => $rcm ],
+ Xml::tags( 'a', $linkAttribs, $image )
+ );
+ $tl .= Xml::element( 'span', [ 'id' => $rcl ], ' ' );
+
+ $items = [];
+ if ( $talkable ) {
+ $items[] = Linker::userTalkLink( $userId, $userText );
+ }
+ if ( $userId ) {
+ $targetPage = SpecialPage::getTitleFor( 'Contributions', $userText );
+ $items[] = Linker::linkKnown( $targetPage,
+ $this->msg( 'contribslink' )->escaped() );
+ }
+ if ( $this->getUser()->isAllowed( 'block' ) ) {
+ $items[] = Linker::blockLink( $userId, $userText );
+ }
+ if ( $userId ) {
+ $userrightsPage = new UserrightsPage();
+ if ( $userrightsPage->userCanChangeRights( User::newFromId( $userId ) ) ) {
+ $targetPage = SpecialPage::getTitleFor( 'Userrights', $userText );
+ $items[] = Linker::linkKnown( $targetPage,
+ $this->msg( 'cleanchanges-changerightslink' )->escaped() );
+ }
+ }
+
+ if ( $items ) {
+ $msg = $this->msg( 'parentheses' )
+ ->rawParams( $this->getLanguage()->pipeList( $items ) )
+ ->escaped();
+ $data = [ "wgUserInfo$rci" => $msg ];
+
+ return [ $tl, $data ];
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Makes aggregated list of contributors for a changes group.
+ * Example: [Usera; AnotherUser; ActiveUser ‎(2×); Userabc ‎(6×)]
+ * @param array $userlinks
+ * @return string
+ */
+ protected function makeUserlinks( $userlinks ) {
+ /*
+ * User with least changes first, and fallback to alphabetical sorting if
+ * multiple users have same number of changes.
+ */
+ krsort( $userlinks );
+ asort( $userlinks );
+
+ $users = [];
+ foreach ( $userlinks as $userlink => $count ) {
+ $text = $userlink;
+ if ( $count > 1 ) {
+ $lang = $this->getLanguage();
+ $count = $lang->formatNum( $count );
+ $text .= "{$lang->getDirMark()}×$count";
+ }
+ array_push( $users, $text );
+ }
+ $text = implode( '; ', $users );
+ return $this->XMLwrapper( 'changedby', "[$text]", 'span', false );
+ }
+
+ /**
+ * @param RCCacheEntry $rc
+ * @param array|null $overrides
+ * @return string
+ */
+ protected function getFlags( $rc, array $overrides = null ) {
+ // @todo We assume all characters are of equal width, which they may be not
+ $map = [
+ # item => field letter-or-something
+ 'new' => [ 'rc_new', self::flag( 'newpage' ) ],
+ 'minor' => [ 'rc_minor', self::flag( 'minor' ) ],
+ 'bot' => [ 'rc_bot', self::flag( 'bot' ) ],
+ ];
+
+ static $nothing = "\xc2\xa0";
+
+ $items = [];
+ foreach ( $map as $item => $data ) {
+ list( $field, $flag ) = $data;
+ $bool = $overrides[$item] ?? $rc->getAttribute( $field );
+ $items[] = $bool ? $flag : $nothing;
+ }
+
+ if ( $this->getUser()->useRCPatrol() ) {
+ if ( isset( $overrides['patrol'] ) ) {
+ $items[] = $overrides['patrol'] ? self::flag( 'unpatrolled' ) : $nothing;
+ } elseif ( $this->showAsUnpatrolled( $rc ) ) {
+ $items[] = self::flag( 'unpatrolled' );
+ } else {
+ $items[] = $nothing;
+ }
+ }
+
+ return implode( '', $items );
+ }
+
+ /**
+ * @param RCCacheEntry $new
+ * @param RCCacheEntry|null $old
+ * @return mixed
+ */
+ protected function getCharacterDifference( $new, $old = null ) {
+ if ( $old === null ) {
+ $old = $new;
+ }
+
+ $newSize = $new->getAttribute( 'rc_new_len' );
+ $oldSize = $old->getAttribute( 'rc_old_len' );
+ if ( $newSize === null || $oldSize === null ) {
+ // @todo Return null instead of string here?
+ return '';
+ }
+
+ return $newSize - $oldSize;
+ }
+
+ /**
+ * @param mixed $szdiff Character difference.
+ * @return string
+ */
+ public function wrapCharacterDifference( $szdiff ) {
+ global $wgRCChangedSizeThreshold;
+ static $cache = [];
+ if ( !isset( $cache[$szdiff] ) ) {
+ // @todo FIXME: Hard coded text (+).
+ $prefix = $szdiff > 0 ? '+' : '';
+ $cache[$szdiff] = $prefix . $this->msg( 'rc-change-size',
+ $this->getLanguage()->formatNum( $szdiff )
+ )->text();
+ }
+
+ $tag = 'span';
+ if ( abs( $szdiff ) > abs( $wgRCChangedSizeThreshold ) ) {
+ $tag = 'strong';
+ }
+
+ if ( $szdiff === 0 ) {
+ return $this->XMLwrapper( 'mw-plusminus-null', $cache[$szdiff], $tag );
+ } elseif ( $szdiff > 0 ) {
+ return $this->XMLwrapper( 'mw-plusminus-pos', $cache[$szdiff], $tag );
+ }
+ return $this->XMLwrapper( 'mw-plusminus-neg', $cache[$szdiff], $tag );
+ }
+
+ /**
+ * @param string $class
+ * @param string $content
+ * @param string $tag
+ * @param bool $escape
+ * @return string
+ */
+ protected function XMLwrapper( $class, $content, $tag = 'span', $escape = true ) {
+ if ( $escape ) {
+ return Xml::element( $tag, [ 'class' => $class ], $content );
+ }
+ return Xml::tags( $tag, [ 'class' => $class ], $content );
+ }
+}