00001 <?php
00002
00006 class RCCacheEntry extends RecentChange {
00007 var $secureName, $link;
00008 var $curlink , $difflink, $lastlink, $usertalklink, $versionlink;
00009 var $userlink, $timestamp, $watched;
00010
00011 static function newFromParent( $rc ) {
00012 $rc2 = new RCCacheEntry;
00013 $rc2->mAttribs = $rc->mAttribs;
00014 $rc2->mExtra = $rc->mExtra;
00015 return $rc2;
00016 }
00017 }
00018
00025 class ChangesList {
00026 # Called by history lists and recent changes
00027 public $skin;
00028 protected $watchlist = false;
00029
00034 public function __construct( $skin ) {
00035 $this->skin = $skin;
00036 $this->preCacheMessages();
00037 }
00038
00046 public static function newFromUser( &$user ) {
00047 $sk = $user->getSkin();
00048 $list = null;
00049 if( wfRunHooks( 'FetchChangesList', array( &$user, &$sk, &$list ) ) ) {
00050 return $user->getOption( 'usenewrc' ) ?
00051 new EnhancedChangesList( $sk ) : new OldChangesList( $sk );
00052 } else {
00053 return $list;
00054 }
00055 }
00056
00061 public function setWatchlistDivs( $value = true ) {
00062 $this->watchlist = $value;
00063 }
00064
00069 private function preCacheMessages() {
00070 if( !isset( $this->message ) ) {
00071 foreach ( explode( ' ', 'cur diff hist last blocklink history ' .
00072 'semicolon-separator pipe-separator' ) as $msg ) {
00073 $this->message[$msg] = wfMsgExt( $msg, array( 'escapenoentities' ) );
00074 }
00075 }
00076 }
00077
00078
00088 protected function recentChangesFlags( $new, $minor, $patrolled, $nothing = ' ', $bot = false ) {
00089 $f = $new ? self::flag( 'newpage' ) : $nothing;
00090 $f .= $minor ? self::flag( 'minor' ) : $nothing;
00091 $f .= $bot ? self::flag( 'bot' ) : $nothing;
00092 $f .= $patrolled ? self::flag( 'unpatrolled' ) : $nothing;
00093 return $f;
00094 }
00095
00105 public static function flag( $key ) {
00106 static $messages = null;
00107 if ( is_null( $messages ) ) {
00108 foreach ( explode( ' ', 'minoreditletter boteditletter newpageletter ' .
00109 'unpatrolledletter recentchanges-label-minor recentchanges-label-bot ' .
00110 'recentchanges-label-newpage recentchanges-label-unpatrolled' ) as $msg ) {
00111 $messages[$msg] = wfMsgExt( $msg, 'escapenoentities' );
00112 }
00113 }
00114 # Inconsistent naming, bleh
00115 if ( $key == 'newpage' || $key == 'unpatrolled' ) {
00116 $key2 = $key;
00117 } else {
00118 $key2 = $key . 'edit';
00119 }
00120 return "<abbr class=\"$key\" title=\""
00121 . $messages["recentchanges-label-$key"] . "\">"
00122 . $messages["${key2}letter"]
00123 . '</abbr>';
00124 }
00125
00134 private static function flagLine( $key ) {
00135 return wfMsgExt( "recentchanges-legend-$key", array( 'escapenoentities',
00136 'replaceafter' ), self::flag( $key ) );
00137 }
00138
00144 public static function flagLegend() {
00145 global $wgGroupPermissions, $wgLang;
00146
00147 $flags = array( self::flagLine( 'newpage' ),
00148 self::flagLine( 'minor' ) );
00149
00150 # Don't show info on bot edits unless there's a bot group of some kind
00151 foreach ( $wgGroupPermissions as $rights ) {
00152 if ( isset( $rights['bot'] ) && $rights['bot'] ) {
00153 $flags[] = self::flagLine( 'bot' );
00154 break;
00155 }
00156 }
00157
00158 if ( self::usePatrol() ) {
00159 $flags[] = self::flagLine( 'unpatrolled' );
00160 }
00161
00162 return '<div class="mw-rc-label-legend">' .
00163 wfMsgExt( 'recentchanges-label-legend', 'parseinline',
00164 $wgLang->commaList( $flags ) ) . '</div>';
00165 }
00166
00171 public function beginRecentChangesList() {
00172 $this->rc_cache = array();
00173 $this->rcMoveIndex = 0;
00174 $this->rcCacheIndex = 0;
00175 $this->lastdate = '';
00176 $this->rclistOpen = false;
00177 return '';
00178 }
00179
00186 public static function showCharacterDifference( $old, $new ) {
00187 global $wgRCChangedSizeThreshold, $wgLang, $wgMiserMode;
00188 $szdiff = $new - $old;
00189
00190 $code = $wgLang->getCode();
00191 static $fastCharDiff = array();
00192 if ( !isset($fastCharDiff[$code]) ) {
00193 $fastCharDiff[$code] = $wgMiserMode || wfMsgNoTrans( 'rc-change-size' ) === '$1';
00194 }
00195
00196 $formatedSize = $wgLang->formatNum($szdiff);
00197
00198 if ( !$fastCharDiff[$code] ) {
00199 $formatedSize = wfMsgExt( 'rc-change-size', array( 'parsemag', 'escape' ), $formatedSize );
00200 }
00201
00202 if( abs( $szdiff ) > abs( $wgRCChangedSizeThreshold ) ) {
00203 $tag = 'strong';
00204 } else {
00205 $tag = 'span';
00206 }
00207 if( $szdiff === 0 ) {
00208 return "<$tag class='mw-plusminus-null'>($formatedSize)</$tag>";
00209 } elseif( $szdiff > 0 ) {
00210 return "<$tag class='mw-plusminus-pos'>(+$formatedSize)</$tag>";
00211 } else {
00212 return "<$tag class='mw-plusminus-neg'>($formatedSize)</$tag>";
00213 }
00214 }
00215
00220 public function endRecentChangesList() {
00221 if( $this->rclistOpen ) {
00222 return "</ul>\n";
00223 } else {
00224 return '';
00225 }
00226 }
00227
00228 public function insertMove( &$s, $rc ) {
00229 # Diff
00230 $s .= '(' . $this->message['diff'] . ') (';
00231 # Hist
00232 $s .= $this->skin->link(
00233 $rc->getMovedToTitle(),
00234 $this->message['hist'],
00235 array(),
00236 array( 'action' => 'history' ),
00237 array( 'known', 'noclasses' )
00238 ) . ') . . ';
00239 # "[[x]] moved to [[y]]"
00240 $msg = ( $rc->mAttribs['rc_type'] == RC_MOVE ) ? '1movedto2' : '1movedto2_redir';
00241 $s .= wfMsg(
00242 $msg,
00243 $this->skin->link(
00244 $rc->getTitle(),
00245 null,
00246 array(),
00247 array( 'redirect' => 'no' ),
00248 array( 'known', 'noclasses' )
00249 ),
00250 $this->skin->link(
00251 $rc->getMovedToTitle(),
00252 null,
00253 array(),
00254 array(),
00255 array( 'known', 'noclasses' )
00256 )
00257 );
00258 }
00259
00260 public function insertDateHeader( &$s, $rc_timestamp ) {
00261 global $wgLang;
00262 # Make date header if necessary
00263 $date = $wgLang->date( $rc_timestamp, true, true );
00264 if( $date != $this->lastdate ) {
00265 if( $this->lastdate != '' ) {
00266 $s .= "</ul>\n";
00267 }
00268 $s .= Xml::element( 'h4', null, $date ) . "\n<ul class=\"special\">";
00269 $this->lastdate = $date;
00270 $this->rclistOpen = true;
00271 }
00272 }
00273
00274 public function insertLog( &$s, $title, $logtype ) {
00275 $logname = LogPage::logName( $logtype );
00276 $s .= '(' . $this->skin->link(
00277 $title,
00278 $logname,
00279 array(),
00280 array(),
00281 array( 'known', 'noclasses' )
00282 ) . ')';
00283 }
00284
00285 public function insertDiffHist( &$s, &$rc, $unpatrolled ) {
00286 # Diff link
00287 if( $rc->mAttribs['rc_type'] == RC_NEW || $rc->mAttribs['rc_type'] == RC_LOG ) {
00288 $diffLink = $this->message['diff'];
00289 } else if( !self::userCan($rc,Revision::DELETED_TEXT) ) {
00290 $diffLink = $this->message['diff'];
00291 } else {
00292 $query = array(
00293 'curid' => $rc->mAttribs['rc_cur_id'],
00294 'diff' => $rc->mAttribs['rc_this_oldid'],
00295 'oldid' => $rc->mAttribs['rc_last_oldid']
00296 );
00297
00298 if( $unpatrolled ) {
00299 $query['rcid'] = $rc->mAttribs['rc_id'];
00300 };
00301
00302 $diffLink = $this->skin->link(
00303 $rc->getTitle(),
00304 $this->message['diff'],
00305 array( 'tabindex' => $rc->counter ),
00306 $query,
00307 array( 'known', 'noclasses' )
00308 );
00309 }
00310 $s .= '(' . $diffLink . $this->message['pipe-separator'];
00311 # History link
00312 $s .= $this->skin->link(
00313 $rc->getTitle(),
00314 $this->message['hist'],
00315 array(),
00316 array(
00317 'curid' => $rc->mAttribs['rc_cur_id'],
00318 'action' => 'history'
00319 ),
00320 array( 'known', 'noclasses' )
00321 );
00322 $s .= ') . . ';
00323 }
00324
00325 public function insertArticleLink( &$s, &$rc, $unpatrolled, $watched ) {
00326 global $wgContLang;
00327 # If it's a new article, there is no diff link, but if it hasn't been
00328 # patrolled yet, we need to give users a way to do so
00329 $params = array();
00330
00331 if ( $unpatrolled && $rc->mAttribs['rc_type'] == RC_NEW ) {
00332 $params['rcid'] = $rc->mAttribs['rc_id'];
00333 }
00334
00335 if( $this->isDeleted($rc,Revision::DELETED_TEXT) ) {
00336 $articlelink = $this->skin->link(
00337 $rc->getTitle(),
00338 null,
00339 array(),
00340 $params,
00341 array( 'known', 'noclasses' )
00342 );
00343 $articlelink = '<span class="history-deleted">' . $articlelink . '</span>';
00344 } else {
00345 $articlelink = ' '. $this->skin->link(
00346 $rc->getTitle(),
00347 null,
00348 array(),
00349 $params,
00350 array( 'known', 'noclasses' )
00351 );
00352 }
00353 # Bolden pages watched by this user
00354 if( $watched ) {
00355 $articlelink = "<strong class=\"mw-watched\">{$articlelink}</strong>";
00356 }
00357 # RTL/LTR marker
00358 $articlelink .= $wgContLang->getDirMark();
00359
00360 wfRunHooks( 'ChangesListInsertArticleLink',
00361 array(&$this, &$articlelink, &$s, &$rc, $unpatrolled, $watched) );
00362
00363 $s .= " $articlelink";
00364 }
00365
00366 public function insertTimestamp( &$s, $rc ) {
00367 global $wgLang;
00368 $s .= $this->message['semicolon-separator'] .
00369 $wgLang->time( $rc->mAttribs['rc_timestamp'], true, true ) . ' . . ';
00370 }
00371
00373 public function insertUserRelatedLinks( &$s, &$rc ) {
00374 if( $this->isDeleted( $rc, Revision::DELETED_USER ) ) {
00375 $s .= ' <span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>';
00376 } else {
00377 $s .= $this->skin->userLink( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
00378 $s .= $this->skin->userToolLinks( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
00379 }
00380 }
00381
00383 public function insertAction( &$s, &$rc ) {
00384 if( $rc->mAttribs['rc_type'] == RC_LOG ) {
00385 if( $this->isDeleted( $rc, LogPage::DELETED_ACTION ) ) {
00386 $s .= ' <span class="history-deleted">' . wfMsgHtml( 'rev-deleted-event' ) . '</span>';
00387 } else {
00388 $s .= ' '.LogPage::actionText( $rc->mAttribs['rc_log_type'], $rc->mAttribs['rc_log_action'],
00389 $rc->getTitle(), $this->skin, LogPage::extractParams( $rc->mAttribs['rc_params'] ), true, true );
00390 }
00391 }
00392 }
00393
00395 public function insertComment( &$s, &$rc ) {
00396 if( $rc->mAttribs['rc_type'] != RC_MOVE && $rc->mAttribs['rc_type'] != RC_MOVE_OVER_REDIRECT ) {
00397 if( $this->isDeleted( $rc, Revision::DELETED_COMMENT ) ) {
00398 $s .= ' <span class="history-deleted">' . wfMsgHtml( 'rev-deleted-comment' ) . '</span>';
00399 } else {
00400 $s .= $this->skin->commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle() );
00401 }
00402 }
00403 }
00404
00409 public static function usePatrol() {
00410 global $wgUser;
00411 return $wgUser->useRCPatrol();
00412 }
00413
00417 protected function numberofWatchingusers( $count ) {
00418 global $wgLang;
00419 static $cache = array();
00420 if( $count > 0 ) {
00421 if( !isset( $cache[$count] ) ) {
00422 $cache[$count] = wfMsgExt( 'number_of_watching_users_RCview',
00423 array('parsemag', 'escape' ), $wgLang->formatNum( $count ) );
00424 }
00425 return $cache[$count];
00426 } else {
00427 return '';
00428 }
00429 }
00430
00437 public static function isDeleted( $rc, $field ) {
00438 return ( $rc->mAttribs['rc_deleted'] & $field ) == $field;
00439 }
00440
00448 public static function userCan( $rc, $field ) {
00449 if( $rc->mAttribs['rc_type'] == RC_LOG ) {
00450 return LogEventsList::userCanBitfield( $rc->mAttribs['rc_deleted'], $field );
00451 } else {
00452 return Revision::userCanBitfield( $rc->mAttribs['rc_deleted'], $field );
00453 }
00454 }
00455
00456 protected function maybeWatchedLink( $link, $watched = false ) {
00457 if( $watched ) {
00458 return '<strong class="mw-watched">' . $link . '</strong>';
00459 } else {
00460 return '<span class="mw-rc-unwatched">' . $link . '</span>';
00461 }
00462 }
00463
00465 public function insertRollback( &$s, &$rc ) {
00466 global $wgUser;
00467 if( !$rc->mAttribs['rc_new'] && $rc->mAttribs['rc_this_oldid'] && $rc->mAttribs['rc_cur_id'] ) {
00468 $page = $rc->getTitle();
00471 if ($wgUser->isAllowed('rollback') && $rc->mAttribs['page_latest'] == $rc->mAttribs['rc_this_oldid'] )
00472 {
00473 $rev = new Revision( array(
00474 'id' => $rc->mAttribs['rc_this_oldid'],
00475 'user' => $rc->mAttribs['rc_user'],
00476 'user_text' => $rc->mAttribs['rc_user_text'],
00477 'deleted' => $rc->mAttribs['rc_deleted']
00478 ) );
00479 $rev->setTitle( $page );
00480 $s .= ' '.$this->skin->generateRollback( $rev );
00481 }
00482 }
00483 }
00484
00485 public function insertTags( &$s, &$rc, &$classes ) {
00486 if ( empty($rc->mAttribs['ts_tags']) )
00487 return;
00488
00489 list($tagSummary, $newClasses) = ChangeTags::formatSummaryRow( $rc->mAttribs['ts_tags'], 'changeslist' );
00490 $classes = array_merge( $classes, $newClasses );
00491 $s .= ' ' . $tagSummary;
00492 }
00493
00494 public function insertExtra( &$s, &$rc, &$classes ) {
00495 ## Empty, used for subclassers to add anything special.
00496 }
00497 }
00498
00499
00503 class OldChangesList extends ChangesList {
00507 public function recentChangesLine( &$rc, $watched = false, $linenumber = null ) {
00508 global $wgLang, $wgRCShowChangedSize, $wgUser;
00509 wfProfileIn( __METHOD__ );
00510 # Should patrol-related stuff be shown?
00511 $unpatrolled = $wgUser->useRCPatrol() && !$rc->mAttribs['rc_patrolled'];
00512
00513 $dateheader = '';
00514 $this->insertDateHeader( $dateheader, $rc->mAttribs['rc_timestamp'] );
00515
00516 $s = '';
00517 $classes = array();
00518
00519 if( $linenumber ) {
00520 if( $linenumber & 1 ) {
00521 $classes[] = 'mw-line-odd';
00522 }
00523 else {
00524 $classes[] = 'mw-line-even';
00525 }
00526 }
00527
00528
00529 if( $rc->mAttribs['rc_type'] == RC_MOVE || $rc->mAttribs['rc_type'] == RC_MOVE_OVER_REDIRECT ) {
00530 $this->insertMove( $s, $rc );
00531
00532 } elseif( $rc->mAttribs['rc_log_type'] ) {
00533 $logtitle = Title::newFromText( 'Log/'.$rc->mAttribs['rc_log_type'], NS_SPECIAL );
00534 $this->insertLog( $s, $logtitle, $rc->mAttribs['rc_log_type'] );
00535
00536 } elseif( $rc->mAttribs['rc_namespace'] == NS_SPECIAL ) {
00537 list( $name, $subpage ) = SpecialPage::resolveAliasWithSubpage( $rc->mAttribs['rc_title'] );
00538 if( $name == 'Log' ) {
00539 $this->insertLog( $s, $rc->getTitle(), $subpage );
00540 }
00541
00542 } else {
00543 $this->insertDiffHist( $s, $rc, $unpatrolled );
00544 # M, N, b and ! (minor, new, bot and unpatrolled)
00545 $s .= $this->recentChangesFlags( $rc->mAttribs['rc_new'], $rc->mAttribs['rc_minor'],
00546 $unpatrolled, '', $rc->mAttribs['rc_bot'] );
00547 $this->insertArticleLink( $s, $rc, $unpatrolled, $watched );
00548 }
00549 # Edit/log timestamp
00550 $this->insertTimestamp( $s, $rc );
00551 # Bytes added or removed
00552 if( $wgRCShowChangedSize ) {
00553 $cd = $rc->getCharacterDifference();
00554 if( $cd != '' ) {
00555 $s .= "$cd . . ";
00556 }
00557 }
00558 # User tool links
00559 $this->insertUserRelatedLinks( $s, $rc );
00560 # Log action text (if any)
00561 $this->insertAction( $s, $rc );
00562 # Edit or log comment
00563 $this->insertComment( $s, $rc );
00564 # Tags
00565 $this->insertTags( $s, $rc, $classes );
00566 # Rollback
00567 $this->insertRollback( $s, $rc );
00568 # For subclasses
00569 $this->insertExtra( $s, $rc, $classes );
00570
00571 # How many users watch this page
00572 if( $rc->numberofWatchingusers > 0 ) {
00573 $s .= ' ' . wfMsgExt( 'number_of_watching_users_RCview',
00574 array( 'parsemag', 'escape' ), $wgLang->formatNum( $rc->numberofWatchingusers ) );
00575 }
00576
00577 if( $this->watchlist ) {
00578 $classes[] = Sanitizer::escapeClass( 'watchlist-'.$rc->mAttribs['rc_namespace'].'-'.$rc->mAttribs['rc_title'] );
00579 }
00580
00581 wfRunHooks( 'OldChangesListRecentChangesLine', array(&$this, &$s, $rc) );
00582
00583 wfProfileOut( __METHOD__ );
00584 return "$dateheader<li class=\"".implode( ' ', $classes )."\">".$s."</li>\n";
00585 }
00586 }
00587
00588
00592 class EnhancedChangesList extends ChangesList {
00597 public function beginRecentChangesList() {
00598 global $wgStylePath, $wgStyleVersion;
00599 $this->rc_cache = array();
00600 $this->rcMoveIndex = 0;
00601 $this->rcCacheIndex = 0;
00602 $this->lastdate = '';
00603 $this->rclistOpen = false;
00604 $script = Html::linkedScript( $wgStylePath . "/common/enhancedchanges.js?$wgStyleVersion" );
00605 return $script;
00606 }
00610 public function recentChangesLine( &$baseRC, $watched = false ) {
00611 global $wgLang, $wgUser;
00612
00613 wfProfileIn( __METHOD__ );
00614
00615 # Create a specialised object
00616 $rc = RCCacheEntry::newFromParent( $baseRC );
00617
00618 # Extract fields from DB into the function scope (rc_xxxx variables)
00619
00620
00621 extract( $rc->mAttribs );
00622 $curIdEq = array( 'curid' => $rc_cur_id );
00623
00624 # If it's a new day, add the headline and flush the cache
00625 $date = $wgLang->date( $rc_timestamp, true );
00626 $ret = '';
00627 if( $date != $this->lastdate ) {
00628 # Process current cache
00629 $ret = $this->recentChangesBlock();
00630 $this->rc_cache = array();
00631 $ret .= Xml::element( 'h4', null, $date );
00632 $this->lastdate = $date;
00633 }
00634
00635 # Should patrol-related stuff be shown?
00636 if( $wgUser->useRCPatrol() ) {
00637 $rc->unpatrolled = !$rc_patrolled;
00638 } else {
00639 $rc->unpatrolled = false;
00640 }
00641
00642 $showdifflinks = true;
00643 # Make article link
00644
00645 if( $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
00646 $msg = ( $rc_type == RC_MOVE ) ? "1movedto2" : "1movedto2_redir";
00647 $clink = wfMsg( $msg, $this->skin->linkKnown( $rc->getTitle(), null,
00648 array(), array( 'redirect' => 'no' ) ),
00649 $this->skin->linkKnown( $rc->getMovedToTitle() ) );
00650
00651 } else if( $rc->unpatrolled && $rc_type == RC_NEW ) {
00652 $clink = $this->skin->linkKnown( $rc->getTitle(), null, array(),
00653 array( 'rcid' => $rc_id ) );
00654
00655 } else if( $rc_type == RC_LOG ) {
00656 if( $rc_log_type ) {
00657 $logtitle = SpecialPage::getTitleFor( 'Log', $rc_log_type );
00658 $clink = '(' . $this->skin->linkKnown( $logtitle,
00659 LogPage::logName($rc_log_type) ) . ')';
00660 } else {
00661 $clink = $this->skin->link( $rc->getTitle() );
00662 }
00663 $watched = false;
00664
00665 } elseif( $rc_namespace == NS_SPECIAL ) {
00666 list( $specialName, $logtype ) = SpecialPage::resolveAliasWithSubpage( $rc_title );
00667 if ( $specialName == 'Log' ) {
00668 # Log updates, etc
00669 $logname = LogPage::logName( $logtype );
00670 $clink = '(' . $this->skin->linkKnown( $rc->getTitle(), $logname ) . ')';
00671 } else {
00672 wfDebug( "Unexpected special page in recentchanges\n" );
00673 $clink = '';
00674 }
00675
00676 } else {
00677 $clink = $this->skin->linkKnown( $rc->getTitle() );
00678 }
00679
00680 # Don't show unusable diff links
00681 if ( !ChangesList::userCan($rc,Revision::DELETED_TEXT) ) {
00682 $showdifflinks = false;
00683 }
00684
00685 $time = $wgLang->time( $rc_timestamp, true, true );
00686 $rc->watched = $watched;
00687 $rc->link = $clink;
00688 $rc->timestamp = $time;
00689 $rc->numberofWatchingusers = $baseRC->numberofWatchingusers;
00690
00691 # Make "cur" and "diff" links. Do not use link(), it is too slow if
00692 # called too many times (50% of CPU time on RecentChanges!).
00693 if( $rc->unpatrolled ) {
00694 $rcIdQuery = array( 'rcid' => $rc_id );
00695 } else {
00696 $rcIdQuery = array();
00697 }
00698 $querycur = $curIdEq + array( 'diff' => '0', 'oldid' => $rc_this_oldid );
00699 $querydiff = $curIdEq + array( 'diff' => $rc_this_oldid, 'oldid' =>
00700 $rc_last_oldid ) + $rcIdQuery;
00701
00702 if( !$showdifflinks ) {
00703 $curLink = $this->message['cur'];
00704 $diffLink = $this->message['diff'];
00705 } else if( in_array( $rc_type, array(RC_NEW,RC_LOG,RC_MOVE,RC_MOVE_OVER_REDIRECT) ) ) {
00706 if ( $rc_type != RC_NEW ) {
00707 $curLink = $this->message['cur'];
00708 } else {
00709 $curUrl = htmlspecialchars( $rc->getTitle()->getLinkUrl( $querycur ) );
00710 $curLink = "<a href=\"$curUrl\" tabindex=\"{$baseRC->counter}\">{$this->message['cur']}</a>";
00711 }
00712 $diffLink = $this->message['diff'];
00713 } else {
00714 $diffUrl = htmlspecialchars( $rc->getTitle()->getLinkUrl( $querydiff ) );
00715 $curUrl = htmlspecialchars( $rc->getTitle()->getLinkUrl( $querycur ) );
00716 $diffLink = "<a href=\"$diffUrl\" tabindex=\"{$baseRC->counter}\">{$this->message['diff']}</a>";
00717 $curLink = "<a href=\"$curUrl\" tabindex=\"{$baseRC->counter}\">{$this->message['cur']}</a>";
00718 }
00719
00720 # Make "last" link
00721 if( !$showdifflinks || !$rc_last_oldid ) {
00722 $lastLink = $this->message['last'];
00723 } else if( $rc_type == RC_LOG || $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
00724 $lastLink = $this->message['last'];
00725 } else {
00726 $lastLink = $this->skin->linkKnown( $rc->getTitle(), $this->message['last'],
00727 array(), $curIdEq + array('diff' => $rc_this_oldid, 'oldid' => $rc_last_oldid) + $rcIdQuery );
00728 }
00729
00730 # Make user links
00731 if( $this->isDeleted($rc,Revision::DELETED_USER) ) {
00732 $rc->userlink = ' <span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>';
00733 } else {
00734 $rc->userlink = $this->skin->userLink( $rc_user, $rc_user_text );
00735 $rc->usertalklink = $this->skin->userToolLinks( $rc_user, $rc_user_text );
00736 }
00737
00738 $rc->lastlink = $lastLink;
00739 $rc->curlink = $curLink;
00740 $rc->difflink = $diffLink;
00741
00742 # Put accumulated information into the cache, for later display
00743 # Page moves go on their own line
00744 $title = $rc->getTitle();
00745 $secureName = $title->getPrefixedDBkey();
00746 if( $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
00747 # Use an @ character to prevent collision with page names
00748 $this->rc_cache['@@' . ($this->rcMoveIndex++)] = array($rc);
00749 } else {
00750 # Logs are grouped by type
00751 if( $rc_type == RC_LOG ){
00752 $secureName = SpecialPage::getTitleFor( 'Log', $rc_log_type )->getPrefixedDBkey();
00753 }
00754 if( !isset( $this->rc_cache[$secureName] ) ) {
00755 $this->rc_cache[$secureName] = array();
00756 }
00757
00758 array_push( $this->rc_cache[$secureName], $rc );
00759 }
00760
00761 wfProfileOut( __METHOD__ );
00762
00763 return $ret;
00764 }
00765
00769 protected function recentChangesBlockGroup( $block ) {
00770 global $wgLang, $wgContLang, $wgRCShowChangedSize;
00771
00772 wfProfileIn( __METHOD__ );
00773
00774 $r = '<table class="mw-enhanced-rc"><tr>';
00775
00776 # Collate list of users
00777 $userlinks = array();
00778 # Other properties
00779 $unpatrolled = false;
00780 $isnew = false;
00781 $curId = $currentRevision = 0;
00782 # Some catalyst variables...
00783 $namehidden = true;
00784 $allLogs = true;
00785 foreach( $block as $rcObj ) {
00786 $oldid = $rcObj->mAttribs['rc_last_oldid'];
00787 if( $rcObj->mAttribs['rc_new'] ) {
00788 $isnew = true;
00789 }
00790
00791
00792 if( !$this->isDeleted( $rcObj, LogPage::DELETED_ACTION ) ) {
00793 $namehidden = false;
00794 }
00795 $u = $rcObj->userlink;
00796 if( !isset( $userlinks[$u] ) ) {
00797 $userlinks[$u] = 0;
00798 }
00799 if( $rcObj->unpatrolled ) {
00800 $unpatrolled = true;
00801 }
00802 if( $rcObj->mAttribs['rc_type'] != RC_LOG ) {
00803 $allLogs = false;
00804 }
00805 # Get the latest entry with a page_id and oldid
00806 # since logs may not have these.
00807 if( !$curId && $rcObj->mAttribs['rc_cur_id'] ) {
00808 $curId = $rcObj->mAttribs['rc_cur_id'];
00809 }
00810 if( !$currentRevision && $rcObj->mAttribs['rc_this_oldid'] ) {
00811 $currentRevision = $rcObj->mAttribs['rc_this_oldid'];
00812 }
00813
00814 $bot = $rcObj->mAttribs['rc_bot'];
00815 $userlinks[$u]++;
00816 }
00817
00818 # Sort the list and convert to text
00819 krsort( $userlinks );
00820 asort( $userlinks );
00821 $users = array();
00822 foreach( $userlinks as $userlink => $count) {
00823 $text = $userlink;
00824 $text .= $wgContLang->getDirMark();
00825 if( $count > 1 ) {
00826 $text .= ' (' . $wgLang->formatNum( $count ) . '×)';
00827 }
00828 array_push( $users, $text );
00829 }
00830
00831 $users = ' <span class="changedby">[' .
00832 implode( $this->message['semicolon-separator'], $users ) . ']</span>';
00833
00834 # ID for JS visibility toggle
00835 $jsid = $this->rcCacheIndex;
00836 # onclick handler to toggle hidden/expanded
00837 $toggleLink = "onclick='toggleVisibility($jsid); return false'";
00838 # Title for <a> tags
00839 $expandTitle = htmlspecialchars( wfMsg( 'rc-enhanced-expand' ) );
00840 $closeTitle = htmlspecialchars( wfMsg( 'rc-enhanced-hide' ) );
00841
00842 $tl = "<span id='mw-rc-openarrow-$jsid' class='mw-changeslist-expanded' style='visibility:hidden'><a href='#' $toggleLink title='$expandTitle'>" . $this->sideArrow() . "</a></span>";
00843 $tl .= "<span id='mw-rc-closearrow-$jsid' class='mw-changeslist-hidden' style='display:none'><a href='#' $toggleLink title='$closeTitle'>" . $this->downArrow() . "</a></span>";
00844 $r .= '<td class="mw-enhanced-rc">'.$tl.' ';
00845
00846 # Main line
00847 $r .= $this->recentChangesFlags( $isnew, false, $unpatrolled, ' ', $bot );
00848
00849 # Timestamp
00850 $r .= ' '.$block[0]->timestamp.' </td><td style="padding:0px;">';
00851
00852 # Article link
00853 if( $namehidden ) {
00854 $r .= ' <span class="history-deleted">' . wfMsgHtml( 'rev-deleted-event' ) . '</span>';
00855 } else if( $allLogs ) {
00856 $r .= $this->maybeWatchedLink( $block[0]->link, $block[0]->watched );
00857 } else {
00858 $this->insertArticleLink( $r, $block[0], $block[0]->unpatrolled, $block[0]->watched );
00859 }
00860
00861 $r .= $wgContLang->getDirMark();
00862
00863 $queryParams['curid'] = $curId;
00864 # Changes message
00865 $n = count($block);
00866 static $nchanges = array();
00867 if ( !isset( $nchanges[$n] ) ) {
00868 $nchanges[$n] = wfMsgExt( 'nchanges', array( 'parsemag', 'escape' ), $wgLang->formatNum( $n ) );
00869 }
00870 # Total change link
00871 $r .= ' ';
00872 if( !$allLogs ) {
00873 $r .= '(';
00874 if( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT ) ) {
00875 $r .= $nchanges[$n];
00876 } else if( $isnew ) {
00877 $r .= $nchanges[$n];
00878 } else {
00879 $params = $queryParams;
00880 $params['diff'] = $currentRevision;
00881 $params['oldid'] = $oldid;
00882
00883 $r .= $this->skin->link(
00884 $block[0]->getTitle(),
00885 $nchanges[$n],
00886 array(),
00887 $params,
00888 array( 'known', 'noclasses' )
00889 );
00890 }
00891 }
00892
00893 # History
00894 if( $allLogs ) {
00895
00896 } else if( $namehidden || !$block[0]->getTitle()->exists() ) {
00897 $r .= $this->message['pipe-separator'] . $this->message['hist'] . ')';
00898 } else {
00899 $params = $queryParams;
00900 $params['action'] = 'history';
00901
00902 $r .= $this->message['pipe-separator'] .
00903 $this->skin->link(
00904 $block[0]->getTitle(),
00905 $this->message['hist'],
00906 array(),
00907 $params,
00908 array( 'known', 'noclasses' )
00909 ) . ')';
00910 }
00911 $r .= ' . . ';
00912
00913 # Character difference (does not apply if only log items)
00914 if( $wgRCShowChangedSize && !$allLogs ) {
00915 $last = 0;
00916 $first = count($block) - 1;
00917 # Some events (like logs) have an "empty" size, so we need to skip those...
00918 while( $last < $first && $block[$last]->mAttribs['rc_new_len'] === null ) {
00919 $last++;
00920 }
00921 while( $first > $last && $block[$first]->mAttribs['rc_old_len'] === null ) {
00922 $first--;
00923 }
00924 # Get net change
00925 $chardiff = $rcObj->getCharacterDifference( $block[$first]->mAttribs['rc_old_len'],
00926 $block[$last]->mAttribs['rc_new_len'] );
00927
00928 if( $chardiff == '' ) {
00929 $r .= ' ';
00930 } else {
00931 $r .= ' ' . $chardiff. ' . . ';
00932 }
00933 }
00934
00935 $r .= $users;
00936 $r .= $this->numberofWatchingusers($block[0]->numberofWatchingusers);
00937
00938 $r .= "</td></tr></table>\n";
00939
00940 # Sub-entries
00941 $r .= '<div id="mw-rc-subentries-'.$jsid.'" class="mw-changeslist-hidden">';
00942 $r .= '<table class="mw-enhanced-rc">';
00943 foreach( $block as $rcObj ) {
00944 # Extract fields from DB into the function scope (rc_xxxx variables)
00945
00946
00947 # Classes to apply -- TODO implement
00948 $classes = array();
00949 extract( $rcObj->mAttribs );
00950
00951 #$r .= '<tr><td valign="top">'.$this->spacerArrow();
00952 $r .= '<tr><td style="vertical-align:top;font-family:monospace; padding:0px;">';
00953 $r .= $this->spacerIndent() . $this->spacerIndent();
00954 $r .= $this->recentChangesFlags( $rc_new, $rc_minor, $rcObj->unpatrolled, ' ', $rc_bot );
00955 $r .= ' </td><td style="vertical-align:top; padding:0px;"><span style="font-family:monospace">';
00956
00957 $params = $queryParams;
00958
00959 if( $rc_this_oldid != 0 ) {
00960 $params['oldid'] = $rc_this_oldid;
00961 }
00962
00963 # Log timestamp
00964 if( $rc_type == RC_LOG ) {
00965 $link = $rcObj->timestamp;
00966 # Revision link
00967 } else if( !ChangesList::userCan($rcObj,Revision::DELETED_TEXT) ) {
00968 $link = '<span class="history-deleted">'.$rcObj->timestamp.'</span> ';
00969 } else {
00970 if ( $rcObj->unpatrolled && $rc_type == RC_NEW) {
00971 $params['rcid'] = $rcObj->mAttribs['rc_id'];
00972 }
00973
00974 $link = $this->skin->link(
00975 $rcObj->getTitle(),
00976 $rcObj->timestamp,
00977 array(),
00978 $params,
00979 array( 'known', 'noclasses' )
00980 );
00981 if( $this->isDeleted($rcObj,Revision::DELETED_TEXT) )
00982 $link = '<span class="history-deleted">'.$link.'</span> ';
00983 }
00984 $r .= $link . '</span>';
00985
00986 if ( !$rc_type == RC_LOG || $rc_type == RC_NEW ) {
00987 $r .= ' (';
00988 $r .= $rcObj->curlink;
00989 $r .= $this->message['pipe-separator'];
00990 $r .= $rcObj->lastlink;
00991 $r .= ')';
00992 }
00993 $r .= ' . . ';
00994
00995 # Character diff
00996 if( $wgRCShowChangedSize ) {
00997 $r .= ( $rcObj->getCharacterDifference() == '' ? '' : $rcObj->getCharacterDifference() . ' . . ' ) ;
00998 }
00999 # User links
01000 $r .= $rcObj->userlink;
01001 $r .= $rcObj->usertalklink;
01002
01003 $this->insertAction( $r, $rcObj );
01004
01005 $this->insertComment( $r, $rcObj );
01006 # Rollback
01007 $this->insertRollback( $r, $rcObj );
01008 # Tags
01009 $this->insertTags( $r, $rcObj, $classes );
01010
01011 $r .= "</td></tr>\n";
01012 }
01013 $r .= "</table></div>\n";
01014
01015 $this->rcCacheIndex++;
01016
01017 wfProfileOut( __METHOD__ );
01018
01019 return $r;
01020 }
01021
01029 protected function arrow( $dir, $alt='', $title='' ) {
01030 global $wgStylePath;
01031 $encUrl = htmlspecialchars( $wgStylePath . '/common/images/Arr_' . $dir . '.png' );
01032 $encAlt = htmlspecialchars( $alt );
01033 $encTitle = htmlspecialchars( $title );
01034 return "<img src=\"$encUrl\" width=\"12\" height=\"12\" alt=\"$encAlt\" title=\"$encTitle\" />";
01035 }
01036
01042 protected function sideArrow() {
01043 global $wgContLang;
01044 $dir = $wgContLang->isRTL() ? 'l' : 'r';
01045 return $this->arrow( $dir, '+', wfMsg( 'rc-enhanced-expand' ) );
01046 }
01047
01053 protected function downArrow() {
01054 return $this->arrow( 'd', '-', wfMsg( 'rc-enhanced-hide' ) );
01055 }
01056
01061 protected function spacerArrow() {
01062 return $this->arrow( '', codepointToUtf8( 0xa0 ) );
01063 }
01064
01069 protected function spacerIndent() {
01070 return ' ';
01071 }
01072
01077 protected function recentChangesBlockLine( $rcObj ) {
01078 global $wgRCShowChangedSize;
01079
01080 wfProfileIn( __METHOD__ );
01081
01082 # Extract fields from DB into the function scope (rc_xxxx variables)
01083
01084
01085 $classes = array();
01086 extract( $rcObj->mAttribs );
01087 $query['curid'] = $rc_cur_id;
01088
01089 $r = '<table class="mw-enhanced-rc"><tr>';
01090 $r .= '<td class="mw-enhanced-rc">' . $this->spacerArrow() . ' ';
01091 # Flag and Timestamp
01092 if( $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
01093 $r .= ' ';
01094 } else {
01095 $r .= $this->recentChangesFlags( $rc_type == RC_NEW, $rc_minor, $rcObj->unpatrolled, ' ', $rc_bot );
01096 }
01097 $r .= ' '.$rcObj->timestamp.' </td><td style="padding:0px;">';
01098 # Article or log link
01099 if( $rc_log_type ) {
01100 $logtitle = Title::newFromText( "Log/$rc_log_type", NS_SPECIAL );
01101 $logname = LogPage::logName( $rc_log_type );
01102 $r .= '(' . $this->skin->link(
01103 $logtitle,
01104 $logname,
01105 array(),
01106 array(),
01107 array( 'known', 'noclasses' )
01108 ) . ')';
01109 } else {
01110 $this->insertArticleLink( $r, $rcObj, $rcObj->unpatrolled, $rcObj->watched );
01111 }
01112 # Diff and hist links
01113 if ( $rc_type != RC_LOG ) {
01114 $r .= ' ('. $rcObj->difflink . $this->message['pipe-separator'];
01115 $query['action'] = 'history';
01116 $r .= $this->skin->link(
01117 $rcObj->getTitle(),
01118 $this->message['hist'],
01119 array(),
01120 $query,
01121 array( 'known', 'noclasses' )
01122 ) . ')';
01123 }
01124 $r .= ' . . ';
01125 # Character diff
01126 if( $wgRCShowChangedSize && ($cd = $rcObj->getCharacterDifference()) ) {
01127 $r .= "$cd . . ";
01128 }
01129 # User/talk
01130 $r .= ' '.$rcObj->userlink . $rcObj->usertalklink;
01131 # Log action (if any)
01132 if( $rc_log_type ) {
01133 if( $this->isDeleted($rcObj,LogPage::DELETED_ACTION) ) {
01134 $r .= ' <span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
01135 } else {
01136 $r .= ' ' . LogPage::actionText( $rc_log_type, $rc_log_action, $rcObj->getTitle(),
01137 $this->skin, LogPage::extractParams($rc_params), true, true );
01138 }
01139 }
01140 $this->insertComment( $r, $rcObj );
01141 $this->insertRollback( $r, $rcObj );
01142 # Tags
01143 $this->insertTags( $r, $rcObj, $classes );
01144 # Show how many people are watching this if enabled
01145 $r .= $this->numberofWatchingusers($rcObj->numberofWatchingusers);
01146
01147 $r .= "</td></tr></table>\n";
01148
01149 wfProfileOut( __METHOD__ );
01150
01151 return $r;
01152 }
01153
01158 protected function recentChangesBlock() {
01159 if( count ( $this->rc_cache ) == 0 ) {
01160 return '';
01161 }
01162
01163 wfProfileIn( __METHOD__ );
01164
01165 $blockOut = '';
01166 foreach( $this->rc_cache as $block ) {
01167 if( count( $block ) < 2 ) {
01168 $blockOut .= $this->recentChangesBlockLine( array_shift( $block ) );
01169 } else {
01170 $blockOut .= $this->recentChangesBlockGroup( $block );
01171 }
01172 }
01173
01174 wfProfileOut( __METHOD__ );
01175
01176 return '<div>'.$blockOut.'</div>';
01177 }
01178
01183 public function endRecentChangesList() {
01184 return $this->recentChangesBlock() . parent::endRecentChangesList();
01185 }
01186
01187 }