MediaWiki
REL1_23
|
00001 <?php 00023 class EnhancedChangesList extends ChangesList { 00024 00028 protected $cacheEntryFactory; 00029 00033 protected $rc_cache; 00034 00038 public function __construct( $obj ) { 00039 if ( $obj instanceof Skin ) { 00040 // @todo: deprecate constructing with Skin 00041 $context = $obj->getContext(); 00042 } else { 00043 if ( ! $obj instanceof IContextSource ) { 00044 throw new MWException( 'EnhancedChangesList must be constructed with a ' 00045 . 'context source or skin.' ); 00046 } 00047 00048 $context = $obj; 00049 } 00050 00051 parent::__construct( $context ); 00052 00053 // message is set by the parent ChangesList class 00054 $this->cacheEntryFactory = new RCCacheEntryFactory( 00055 $context, 00056 $this->message 00057 ); 00058 } 00059 00064 public function beginRecentChangesList() { 00065 $this->rc_cache = array(); 00066 $this->rcMoveIndex = 0; 00067 $this->rcCacheIndex = 0; 00068 $this->lastdate = ''; 00069 $this->rclistOpen = false; 00070 $this->getOutput()->addModuleStyles( array( 00071 'mediawiki.special.changeslist', 00072 'mediawiki.special.changeslist.enhanced', 00073 ) ); 00074 $this->getOutput()->addModules( array( 00075 'jquery.makeCollapsible', 00076 'mediawiki.icon', 00077 ) ); 00078 00079 return '<div class="mw-changeslist">'; 00080 } 00081 00090 public function recentChangesLine( &$baseRC, $watched = false ) { 00091 wfProfileIn( __METHOD__ ); 00092 00093 # If it's a new day, add the headline and flush the cache 00094 $date = $this->getLanguage()->userDate( 00095 $baseRC->mAttribs['rc_timestamp'], 00096 $this->getUser() 00097 ); 00098 00099 $ret = ''; 00100 00101 if ( $date != $this->lastdate ) { 00102 # Process current cache 00103 $ret = $this->recentChangesBlock(); 00104 $this->rc_cache = array(); 00105 $ret .= Xml::element( 'h4', null, $date ) . "\n"; 00106 $this->lastdate = $date; 00107 } 00108 00109 $cacheEntry = $this->cacheEntryFactory->newFromRecentChange( $baseRC, $watched ); 00110 $this->addCacheEntry( $cacheEntry ); 00111 00112 wfProfileOut( __METHOD__ ); 00113 00114 return $ret; 00115 } 00116 00123 protected function addCacheEntry( RCCacheEntry $cacheEntry ) { 00124 $title = $cacheEntry->getTitle(); 00125 $secureName = $title->getPrefixedDBkey(); 00126 00127 $type = $cacheEntry->mAttribs['rc_type']; 00128 00129 if ( $type == RC_MOVE || $type == RC_MOVE_OVER_REDIRECT ) { 00130 # Use an @ character to prevent collision with page names 00131 $this->rc_cache['@@' . ( $this->rcMoveIndex++ )] = array( $cacheEntry ); 00132 } else { 00133 # Logs are grouped by type 00134 if ( $type == RC_LOG ) { 00135 $secureName = SpecialPage::getTitleFor( 00136 'Log', 00137 $cacheEntry->mAttribs['rc_log_type'] 00138 )->getPrefixedDBkey(); 00139 } 00140 if ( !isset( $this->rc_cache[$secureName] ) ) { 00141 $this->rc_cache[$secureName] = array(); 00142 } 00143 00144 array_push( $this->rc_cache[$secureName], $cacheEntry ); 00145 } 00146 } 00147 00153 protected function recentChangesBlockGroup( $block ) { 00154 global $wgRCShowChangedSize; 00155 00156 wfProfileIn( __METHOD__ ); 00157 00158 # Add the namespace and title of the block as part of the class 00159 $classes = array( 'mw-collapsible', 'mw-collapsed', 'mw-enhanced-rc' ); 00160 if ( $block[0]->mAttribs['rc_log_type'] ) { 00161 # Log entry 00162 $classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-' 00163 . $block[0]->mAttribs['rc_log_type'] ); 00164 } else { 00165 $classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns' 00166 . $block[0]->mAttribs['rc_namespace'] . '-' . $block[0]->mAttribs['rc_title'] ); 00167 } 00168 $classes[] = $block[0]->watched && $block[0]->mAttribs['rc_timestamp'] >= $block[0]->watched 00169 ? 'mw-changeslist-line-watched' : 'mw-changeslist-line-not-watched'; 00170 $r = Html::openElement( 'table', array( 'class' => $classes ) ) . 00171 Html::openElement( 'tr' ); 00172 00173 # Collate list of users 00174 $userlinks = array(); 00175 # Other properties 00176 $unpatrolled = false; 00177 $isnew = false; 00178 $allBots = true; 00179 $allMinors = true; 00180 $curId = $currentRevision = 0; 00181 # Some catalyst variables... 00182 $namehidden = true; 00183 $allLogs = true; 00184 $oldid = ''; 00185 foreach ( $block as $rcObj ) { 00186 $oldid = $rcObj->mAttribs['rc_last_oldid']; 00187 if ( $rcObj->mAttribs['rc_type'] == RC_NEW ) { 00188 $isnew = true; 00189 } 00190 // If all log actions to this page were hidden, then don't 00191 // give the name of the affected page for this block! 00192 if ( !$this->isDeleted( $rcObj, LogPage::DELETED_ACTION ) ) { 00193 $namehidden = false; 00194 } 00195 $u = $rcObj->userlink; 00196 if ( !isset( $userlinks[$u] ) ) { 00197 $userlinks[$u] = 0; 00198 } 00199 if ( $rcObj->unpatrolled ) { 00200 $unpatrolled = true; 00201 } 00202 if ( $rcObj->mAttribs['rc_type'] != RC_LOG ) { 00203 $allLogs = false; 00204 } 00205 # Get the latest entry with a page_id and oldid 00206 # since logs may not have these. 00207 if ( !$curId && $rcObj->mAttribs['rc_cur_id'] ) { 00208 $curId = $rcObj->mAttribs['rc_cur_id']; 00209 } 00210 if ( !$currentRevision && $rcObj->mAttribs['rc_this_oldid'] ) { 00211 $currentRevision = $rcObj->mAttribs['rc_this_oldid']; 00212 } 00213 00214 if ( !$rcObj->mAttribs['rc_bot'] ) { 00215 $allBots = false; 00216 } 00217 if ( !$rcObj->mAttribs['rc_minor'] ) { 00218 $allMinors = false; 00219 } 00220 00221 $userlinks[$u]++; 00222 } 00223 00224 # Sort the list and convert to text 00225 krsort( $userlinks ); 00226 asort( $userlinks ); 00227 $users = array(); 00228 foreach ( $userlinks as $userlink => $count ) { 00229 $text = $userlink; 00230 $text .= $this->getLanguage()->getDirMark(); 00231 if ( $count > 1 ) { 00232 // @todo FIXME: Hardcoded '×'. Should be a message. 00233 $formattedCount = $this->getLanguage()->formatNum( $count ) . '×'; 00234 $text .= ' ' . $this->msg( 'parentheses' )->rawParams( $formattedCount )->escaped(); 00235 } 00236 array_push( $users, $text ); 00237 } 00238 00239 $users = ' <span class="changedby">' 00240 . $this->msg( 'brackets' )->rawParams( 00241 implode( $this->message['semicolon-separator'], $users ) 00242 )->escaped() . '</span>'; 00243 00244 $tl = '<span class="mw-collapsible-toggle mw-collapsible-arrow ' . 00245 'mw-enhancedchanges-arrow mw-enhancedchanges-arrow-space"></span>'; 00246 $r .= "<td>$tl</td>"; 00247 00248 # Main line 00249 $r .= '<td class="mw-enhanced-rc">' . $this->recentChangesFlags( array( 00250 'newpage' => $isnew, # show, when one have this flag 00251 'minor' => $allMinors, # show only, when all have this flag 00252 'unpatrolled' => $unpatrolled, # show, when one have this flag 00253 'bot' => $allBots, # show only, when all have this flag 00254 ) ); 00255 00256 # Timestamp 00257 $r .= ' ' . $block[0]->timestamp . ' </td><td>'; 00258 00259 # Article link 00260 if ( $namehidden ) { 00261 $r .= ' <span class="history-deleted">' . 00262 $this->msg( 'rev-deleted-event' )->escaped() . '</span>'; 00263 } elseif ( $allLogs ) { 00264 $r .= $this->maybeWatchedLink( $block[0]->link, $block[0]->watched ); 00265 } else { 00266 $this->insertArticleLink( $r, $block[0], $block[0]->unpatrolled, $block[0]->watched ); 00267 } 00268 00269 $r .= $this->getLanguage()->getDirMark(); 00270 00271 $queryParams['curid'] = $curId; 00272 00273 # Changes message 00274 static $nchanges = array(); 00275 static $sinceLastVisitMsg = array(); 00276 00277 $n = count( $block ); 00278 if ( !isset( $nchanges[$n] ) ) { 00279 $nchanges[$n] = $this->msg( 'nchanges' )->numParams( $n )->escaped(); 00280 } 00281 00282 $sinceLast = 0; 00283 $unvisitedOldid = null; 00285 foreach ( $block as $rcObj ) { 00286 // Same logic as below inside main foreach 00287 if ( $rcObj->watched && $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched ) { 00288 $sinceLast++; 00289 $unvisitedOldid = $rcObj->mAttribs['rc_last_oldid']; 00290 } 00291 } 00292 if ( !isset( $sinceLastVisitMsg[$sinceLast] ) ) { 00293 $sinceLastVisitMsg[$sinceLast] = 00294 $this->msg( 'enhancedrc-since-last-visit' )->numParams( $sinceLast )->escaped(); 00295 } 00296 00297 # Total change link 00298 $r .= ' '; 00299 $logtext = ''; 00301 $block0 = $block[0]; 00302 if ( !$allLogs ) { 00303 if ( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ) { 00304 $logtext .= $nchanges[$n]; 00305 } elseif ( $isnew ) { 00306 $logtext .= $nchanges[$n]; 00307 } else { 00308 $logtext .= Linker::link( 00309 $block0->getTitle(), 00310 $nchanges[$n], 00311 array(), 00312 $queryParams + array( 00313 'diff' => $currentRevision, 00314 'oldid' => $oldid, 00315 ), 00316 array( 'known', 'noclasses' ) 00317 ); 00318 if ( $sinceLast > 0 && $sinceLast < $n ) { 00319 $logtext .= $this->message['pipe-separator'] . Linker::link( 00320 $block0->getTitle(), 00321 $sinceLastVisitMsg[$sinceLast], 00322 array(), 00323 $queryParams + array( 00324 'diff' => $currentRevision, 00325 'oldid' => $unvisitedOldid, 00326 ), 00327 array( 'known', 'noclasses' ) 00328 ); 00329 } 00330 } 00331 } 00332 00333 # History 00334 if ( $allLogs ) { 00335 // don't show history link for logs 00336 } elseif ( $namehidden || !$block0->getTitle()->exists() ) { 00337 $logtext .= $this->message['pipe-separator'] . $this->message['enhancedrc-history']; 00338 } else { 00339 $params = $queryParams; 00340 $params['action'] = 'history'; 00341 00342 $logtext .= $this->message['pipe-separator'] . 00343 Linker::linkKnown( 00344 $block0->getTitle(), 00345 $this->message['enhancedrc-history'], 00346 array(), 00347 $params 00348 ); 00349 } 00350 00351 if ( $logtext !== '' ) { 00352 $r .= $this->msg( 'parentheses' )->rawParams( $logtext )->escaped(); 00353 } 00354 00355 $r .= ' <span class="mw-changeslist-separator">. .</span> '; 00356 00357 # Character difference (does not apply if only log items) 00358 if ( $wgRCShowChangedSize && !$allLogs ) { 00359 $last = 0; 00360 $first = count( $block ) - 1; 00361 # Some events (like logs) have an "empty" size, so we need to skip those... 00362 while ( $last < $first && $block[$last]->mAttribs['rc_new_len'] === null ) { 00363 $last++; 00364 } 00365 while ( $first > $last && $block[$first]->mAttribs['rc_old_len'] === null ) { 00366 $first--; 00367 } 00368 # Get net change 00369 $chardiff = $this->formatCharacterDifference( $block[$first], $block[$last] ); 00370 00371 if ( $chardiff == '' ) { 00372 $r .= ' '; 00373 } else { 00374 $r .= ' ' . $chardiff . ' <span class="mw-changeslist-separator">. .</span> '; 00375 } 00376 } 00377 00378 $r .= $users; 00379 $r .= $this->numberofWatchingusers( $block0->numberofWatchingusers ); 00380 $r .= '</td></tr>'; 00381 00382 # Sub-entries 00383 foreach ( $block as $rcObj ) { 00384 # Classes to apply -- TODO implement 00385 $classes = array(); 00386 $type = $rcObj->mAttribs['rc_type']; 00387 00388 $trClass = $rcObj->watched && $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched 00389 ? ' class="mw-enhanced-watched"' : ''; 00390 00391 $r .= '<tr' . $trClass . '><td></td><td class="mw-enhanced-rc">'; 00392 $r .= $this->recentChangesFlags( array( 00393 'newpage' => $type == RC_NEW, 00394 'minor' => $rcObj->mAttribs['rc_minor'], 00395 'unpatrolled' => $rcObj->unpatrolled, 00396 'bot' => $rcObj->mAttribs['rc_bot'], 00397 ) ); 00398 $r .= ' </td><td class="mw-enhanced-rc-nested"><span class="mw-enhanced-rc-time">'; 00399 00400 $params = $queryParams; 00401 00402 if ( $rcObj->mAttribs['rc_this_oldid'] != 0 ) { 00403 $params['oldid'] = $rcObj->mAttribs['rc_this_oldid']; 00404 } 00405 00406 # Log timestamp 00407 if ( $type == RC_LOG ) { 00408 $link = $rcObj->timestamp; 00409 # Revision link 00410 } elseif ( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ) { 00411 $link = '<span class="history-deleted">' . $rcObj->timestamp . '</span> '; 00412 } else { 00413 00414 $link = Linker::linkKnown( 00415 $rcObj->getTitle(), 00416 $rcObj->timestamp, 00417 array(), 00418 $params 00419 ); 00420 if ( $this->isDeleted( $rcObj, Revision::DELETED_TEXT ) ) { 00421 $link = '<span class="history-deleted">' . $link . '</span> '; 00422 } 00423 } 00424 $r .= $link . '</span>'; 00425 00426 if ( !$type == RC_LOG || $type == RC_NEW ) { 00427 $r .= ' ' . $this->msg( 'parentheses' )->rawParams( 00428 $rcObj->curlink . 00429 $this->message['pipe-separator'] . 00430 $rcObj->lastlink 00431 )->escaped(); 00432 } 00433 $r .= ' <span class="mw-changeslist-separator">. .</span> '; 00434 00435 # Character diff 00436 if ( $wgRCShowChangedSize ) { 00437 $cd = $this->formatCharacterDifference( $rcObj ); 00438 if ( $cd !== '' ) { 00439 $r .= $cd . ' <span class="mw-changeslist-separator">. .</span> '; 00440 } 00441 } 00442 00443 if ( $rcObj->mAttribs['rc_type'] == RC_LOG ) { 00444 $r .= $this->insertLogEntry( $rcObj ); 00445 } else { 00446 # User links 00447 $r .= $rcObj->userlink; 00448 $r .= $rcObj->usertalklink; 00449 $r .= $this->insertComment( $rcObj ); 00450 } 00451 00452 # Rollback 00453 $this->insertRollback( $r, $rcObj ); 00454 # Tags 00455 $this->insertTags( $r, $rcObj, $classes ); 00456 00457 $r .= "</td></tr>\n"; 00458 } 00459 $r .= "</table>\n"; 00460 00461 $this->rcCacheIndex++; 00462 00463 wfProfileOut( __METHOD__ ); 00464 00465 return $r; 00466 } 00467 00475 protected function arrow( $dir, $alt = '', $title = '' ) { 00476 global $wgStylePath; 00477 $encUrl = htmlspecialchars( $wgStylePath . '/common/images/Arr_' . $dir . '.png' ); 00478 $encAlt = htmlspecialchars( $alt ); 00479 $encTitle = htmlspecialchars( $title ); 00480 00481 return "<img src=\"$encUrl\" width=\"12\" height=\"12\" alt=\"$encAlt\" title=\"$encTitle\" />"; 00482 } 00483 00489 protected function sideArrow() { 00490 $dir = $this->getLanguage()->isRTL() ? 'l' : 'r'; 00491 00492 return $this->arrow( $dir, '+', $this->msg( 'rc-enhanced-expand' )->text() ); 00493 } 00494 00500 protected function downArrow() { 00501 return $this->arrow( 'd', '-', $this->msg( 'rc-enhanced-hide' )->text() ); 00502 } 00503 00508 protected function spacerArrow() { 00509 return $this->arrow( '', codepointToUtf8( 0xa0 ) ); // non-breaking space 00510 } 00511 00518 protected function recentChangesBlockLine( $rcObj ) { 00519 global $wgRCShowChangedSize; 00520 00521 wfProfileIn( __METHOD__ ); 00522 $query['curid'] = $rcObj->mAttribs['rc_cur_id']; 00523 00524 $type = $rcObj->mAttribs['rc_type']; 00525 $logType = $rcObj->mAttribs['rc_log_type']; 00526 $classes = array( 'mw-enhanced-rc' ); 00527 if ( $logType ) { 00528 # Log entry 00529 $classes[] = Sanitizer::escapeClass( 'mw-changeslist-log-' . $logType ); 00530 } else { 00531 $classes[] = Sanitizer::escapeClass( 'mw-changeslist-ns' . 00532 $rcObj->mAttribs['rc_namespace'] . '-' . $rcObj->mAttribs['rc_title'] ); 00533 } 00534 $classes[] = $rcObj->watched && $rcObj->mAttribs['rc_timestamp'] >= $rcObj->watched 00535 ? 'mw-changeslist-line-watched' : 'mw-changeslist-line-not-watched'; 00536 $r = Html::openElement( 'table', array( 'class' => $classes ) ) . 00537 Html::openElement( 'tr' ); 00538 00539 $r .= '<td class="mw-enhanced-rc"><span class="mw-enhancedchanges-arrow-space"></span>'; 00540 # Flag and Timestamp 00541 if ( $type == RC_MOVE || $type == RC_MOVE_OVER_REDIRECT ) { 00542 $r .= $this->recentChangesFlags( array() ); // no flags, but need the placeholders 00543 } else { 00544 $r .= $this->recentChangesFlags( array( 00545 'newpage' => $type == RC_NEW, 00546 'minor' => $rcObj->mAttribs['rc_minor'], 00547 'unpatrolled' => $rcObj->unpatrolled, 00548 'bot' => $rcObj->mAttribs['rc_bot'], 00549 ) ); 00550 } 00551 $r .= ' ' . $rcObj->timestamp . ' </td><td>'; 00552 # Article or log link 00553 if ( $logType ) { 00554 $logPage = new LogPage( $logType ); 00555 $logTitle = SpecialPage::getTitleFor( 'Log', $logType ); 00556 $logName = $logPage->getName()->escaped(); 00557 $r .= $this->msg( 'parentheses' ) 00558 ->rawParams( Linker::linkKnown( $logTitle, $logName ) )->escaped(); 00559 } else { 00560 $this->insertArticleLink( $r, $rcObj, $rcObj->unpatrolled, $rcObj->watched ); 00561 } 00562 # Diff and hist links 00563 if ( $type != RC_LOG ) { 00564 $query['action'] = 'history'; 00565 $r .= ' ' . $this->msg( 'parentheses' ) 00566 ->rawParams( $rcObj->difflink . $this->message['pipe-separator'] . Linker::linkKnown( 00567 $rcObj->getTitle(), 00568 $this->message['hist'], 00569 array(), 00570 $query 00571 ) )->escaped(); 00572 } 00573 $r .= ' <span class="mw-changeslist-separator">. .</span> '; 00574 # Character diff 00575 if ( $wgRCShowChangedSize ) { 00576 $cd = $this->formatCharacterDifference( $rcObj ); 00577 if ( $cd !== '' ) { 00578 $r .= $cd . ' <span class="mw-changeslist-separator">. .</span> '; 00579 } 00580 } 00581 00582 if ( $type == RC_LOG ) { 00583 $r .= $this->insertLogEntry( $rcObj ); 00584 } else { 00585 $r .= ' ' . $rcObj->userlink . $rcObj->usertalklink; 00586 $r .= $this->insertComment( $rcObj ); 00587 $this->insertRollback( $r, $rcObj ); 00588 } 00589 00590 # Tags 00591 $this->insertTags( $r, $rcObj, $classes ); 00592 # Show how many people are watching this if enabled 00593 $r .= $this->numberofWatchingusers( $rcObj->numberofWatchingusers ); 00594 00595 $r .= "</td></tr></table>\n"; 00596 00597 wfProfileOut( __METHOD__ ); 00598 00599 return $r; 00600 } 00601 00608 protected function recentChangesBlock() { 00609 if ( count( $this->rc_cache ) == 0 ) { 00610 return ''; 00611 } 00612 00613 wfProfileIn( __METHOD__ ); 00614 00615 $blockOut = ''; 00616 foreach ( $this->rc_cache as $block ) { 00617 if ( count( $block ) < 2 ) { 00618 $blockOut .= $this->recentChangesBlockLine( array_shift( $block ) ); 00619 } else { 00620 $blockOut .= $this->recentChangesBlockGroup( $block ); 00621 } 00622 } 00623 00624 wfProfileOut( __METHOD__ ); 00625 00626 return '<div>' . $blockOut . '</div>'; 00627 } 00628 00634 public function endRecentChangesList() { 00635 return $this->recentChangesBlock() . '</div>'; 00636 } 00637 }