MediaWiki
REL1_19
|
00001 <?php 00030 class SpecialContributions extends SpecialPage { 00031 00032 protected $opts; 00033 00034 public function __construct() { 00035 parent::__construct( 'Contributions' ); 00036 } 00037 00038 public function execute( $par ) { 00039 $this->setHeaders(); 00040 $this->outputHeader(); 00041 $out = $this->getOutput(); 00042 $out->addModuleStyles( 'mediawiki.special' ); 00043 00044 $this->opts = array(); 00045 $request = $this->getRequest(); 00046 00047 if ( $par == 'newbies' ) { 00048 $target = 'newbies'; 00049 $this->opts['contribs'] = 'newbie'; 00050 } elseif ( $par !== null ) { 00051 $target = $par; 00052 } else { 00053 $target = $request->getVal( 'target' ); 00054 } 00055 00056 // check for radiobox 00057 if ( $request->getVal( 'contribs' ) == 'newbie' ) { 00058 $target = 'newbies'; 00059 $this->opts['contribs'] = 'newbie'; 00060 } else { 00061 $this->opts['contribs'] = 'user'; 00062 } 00063 00064 $this->opts['deletedOnly'] = $request->getBool( 'deletedOnly' ); 00065 00066 if ( !strlen( $target ) ) { 00067 $out->addHTML( $this->getForm() ); 00068 return; 00069 } 00070 00071 $user = $this->getUser(); 00072 00073 $this->opts['limit'] = $request->getInt( 'limit', $user->getOption( 'rclimit' ) ); 00074 $this->opts['target'] = $target; 00075 $this->opts['topOnly'] = $request->getBool( 'topOnly' ); 00076 00077 $nt = Title::makeTitleSafe( NS_USER, $target ); 00078 if ( !$nt ) { 00079 $out->addHTML( $this->getForm() ); 00080 return; 00081 } 00082 $userObj = User::newFromName( $nt->getText(), false ); 00083 if ( !$userObj ) { 00084 $out->addHTML( $this->getForm() ); 00085 return; 00086 } 00087 $id = $userObj->getID(); 00088 00089 if ( $this->opts['contribs'] != 'newbie' ) { 00090 $target = $nt->getText(); 00091 $out->addSubtitle( $this->contributionsSub( $userObj ) ); 00092 $out->setHTMLTitle( $this->msg( 'pagetitle', $this->msg( 'contributions-title', $target )->plain() ) ); 00093 $this->getSkin()->setRelevantUser( $userObj ); 00094 } else { 00095 $out->addSubtitle( $this->msg( 'sp-contributions-newbies-sub' ) ); 00096 $out->setHTMLTitle( $this->msg( 'pagetitle', $this->msg( 'sp-contributions-newbies-title' )->plain() ) ); 00097 } 00098 00099 if ( ( $ns = $request->getVal( 'namespace', null ) ) !== null && $ns !== '' ) { 00100 $this->opts['namespace'] = intval( $ns ); 00101 } else { 00102 $this->opts['namespace'] = ''; 00103 } 00104 00105 $this->opts['associated'] = $request->getBool( 'associated' ); 00106 00107 $this->opts['nsInvert'] = (bool) $request->getVal( 'nsInvert' ); 00108 00109 $this->opts['tagfilter'] = (string) $request->getVal( 'tagfilter' ); 00110 00111 // Allows reverts to have the bot flag in recent changes. It is just here to 00112 // be passed in the form at the top of the page 00113 if ( $user->isAllowed( 'markbotedits' ) && $request->getBool( 'bot' ) ) { 00114 $this->opts['bot'] = '1'; 00115 } 00116 00117 $skip = $request->getText( 'offset' ) || $request->getText( 'dir' ) == 'prev'; 00118 # Offset overrides year/month selection 00119 if ( $skip ) { 00120 $this->opts['year'] = ''; 00121 $this->opts['month'] = ''; 00122 } else { 00123 $this->opts['year'] = $request->getIntOrNull( 'year' ); 00124 $this->opts['month'] = $request->getIntOrNull( 'month' ); 00125 } 00126 00127 $feedType = $request->getVal( 'feed' ); 00128 if ( $feedType ) { 00129 // Maintain some level of backwards compatability 00130 // If people request feeds using the old parameters, redirect to API 00131 $apiParams = array( 00132 'action' => 'feedcontributions', 00133 'feedformat' => $feedType, 00134 'user' => $target, 00135 ); 00136 if ( $this->opts['topOnly'] ) { 00137 $apiParams['toponly'] = true; 00138 } 00139 if ( $this->opts['deletedOnly'] ) { 00140 $apiParams['deletedonly'] = true; 00141 } 00142 if ( $this->opts['tagfilter'] !== '' ) { 00143 $apiParams['tagfilter'] = $this->opts['tagfilter']; 00144 } 00145 if ( $this->opts['namespace'] !== '' ) { 00146 $apiParams['namespace'] = $this->opts['namespace']; 00147 } 00148 if ( $this->opts['year'] !== null ) { 00149 $apiParams['year'] = $this->opts['year']; 00150 } 00151 if ( $this->opts['month'] !== null ) { 00152 $apiParams['month'] = $this->opts['month']; 00153 } 00154 00155 $url = wfScript( 'api' ) . '?' . wfArrayToCGI( $apiParams ); 00156 00157 $out->redirect( $url, '301' ); 00158 return; 00159 } 00160 00161 // Add RSS/atom links 00162 $this->addFeedLinks( array( 'action' => 'feedcontributions', 'user' => $target ) ); 00163 00164 if ( wfRunHooks( 'SpecialContributionsBeforeMainOutput', array( $id ) ) ) { 00165 00166 $out->addHTML( $this->getForm() ); 00167 00168 $pager = new ContribsPager( $this->getContext(), array( 00169 'target' => $target, 00170 'contribs' => $this->opts['contribs'], 00171 'namespace' => $this->opts['namespace'], 00172 'year' => $this->opts['year'], 00173 'month' => $this->opts['month'], 00174 'deletedOnly' => $this->opts['deletedOnly'], 00175 'topOnly' => $this->opts['topOnly'], 00176 'nsInvert' => $this->opts['nsInvert'], 00177 'associated' => $this->opts['associated'], 00178 ) ); 00179 if ( !$pager->getNumRows() ) { 00180 $out->addWikiMsg( 'nocontribs', $target ); 00181 } else { 00182 # Show a message about slave lag, if applicable 00183 $lag = wfGetLB()->safeGetLag( $pager->getDatabase() ); 00184 if ( $lag > 0 ) 00185 $out->showLagWarning( $lag ); 00186 00187 $out->addHTML( 00188 '<p>' . $pager->getNavigationBar() . '</p>' . 00189 $pager->getBody() . 00190 '<p>' . $pager->getNavigationBar() . '</p>' 00191 ); 00192 } 00193 $out->preventClickjacking( $pager->getPreventClickjacking() ); 00194 00195 # Show the appropriate "footer" message - WHOIS tools, etc. 00196 if ( $this->opts['contribs'] != 'newbie' ) { 00197 $message = 'sp-contributions-footer'; 00198 if ( IP::isIPAddress( $target ) ) { 00199 $message = 'sp-contributions-footer-anon'; 00200 } else { 00201 if ( $userObj->isAnon() ) { 00202 // No message for non-existing users 00203 return; 00204 } 00205 } 00206 00207 if ( !$this->msg( $message, $target )->isDisabled() ) { 00208 $out->wrapWikiMsg( 00209 "<div class='mw-contributions-footer'>\n$1\n</div>", 00210 array( $message, $target ) ); 00211 } 00212 } 00213 } 00214 } 00215 00222 protected function contributionsSub( $userObj ) { 00223 if ( $userObj->isAnon() ) { 00224 $user = htmlspecialchars( $userObj->getName() ); 00225 } else { 00226 $user = Linker::link( $userObj->getUserPage(), htmlspecialchars( $userObj->getName() ) ); 00227 } 00228 $nt = $userObj->getUserPage(); 00229 $talk = $userObj->getTalkPage(); 00230 if ( $talk ) { 00231 $tools = $this->getUserLinks( $nt, $talk, $userObj ); 00232 $links = $this->getLanguage()->pipeList( $tools ); 00233 00234 // Show a note if the user is blocked and display the last block log entry. 00235 if ( $userObj->isBlocked() ) { 00236 $out = $this->getOutput(); // showLogExtract() wants first parameter by reference 00237 LogEventsList::showLogExtract( 00238 $out, 00239 'block', 00240 $nt, 00241 '', 00242 array( 00243 'lim' => 1, 00244 'showIfEmpty' => false, 00245 'msgKey' => array( 00246 $userObj->isAnon() ? 00247 'sp-contributions-blocked-notice-anon' : 00248 'sp-contributions-blocked-notice', 00249 $userObj->getName() # Support GENDER in 'sp-contributions-blocked-notice' 00250 ), 00251 'offset' => '' # don't use WebRequest parameter offset 00252 ) 00253 ); 00254 } 00255 } 00256 00257 // Old message 'contribsub' had one parameter, but that doesn't work for 00258 // languages that want to put the "for" bit right after $user but before 00259 // $links. If 'contribsub' is around, use it for reverse compatibility, 00260 // otherwise use 'contribsub2'. 00261 $oldMsg = $this->msg( 'contribsub' ); 00262 if ( $oldMsg->exists() ) { 00263 return $oldMsg->rawParams( "$user ($links)" ); 00264 } else { 00265 return $this->msg( 'contribsub2' )->rawParams( $user, $links ); 00266 } 00267 } 00268 00276 public function getUserLinks( Title $userpage, Title $talkpage, User $target ) { 00277 00278 $id = $target->getId(); 00279 $username = $target->getName(); 00280 00281 $tools[] = Linker::link( $talkpage, $this->msg( 'sp-contributions-talk' )->escaped() ); 00282 00283 if ( ( $id !== null ) || ( $id === null && IP::isIPAddress( $username ) ) ) { 00284 if ( $this->getUser()->isAllowed( 'block' ) ) { # Block / Change block / Unblock links 00285 if ( $target->isBlocked() ) { 00286 $tools[] = Linker::linkKnown( # Change block link 00287 SpecialPage::getTitleFor( 'Block', $username ), 00288 $this->msg( 'change-blocklink' )->escaped() 00289 ); 00290 $tools[] = Linker::linkKnown( # Unblock link 00291 SpecialPage::getTitleFor( 'Unblock', $username ), 00292 $this->msg( 'unblocklink' )->escaped() 00293 ); 00294 } else { # User is not blocked 00295 $tools[] = Linker::linkKnown( # Block link 00296 SpecialPage::getTitleFor( 'Block', $username ), 00297 $this->msg( 'blocklink' )->escaped() 00298 ); 00299 } 00300 } 00301 # Block log link 00302 $tools[] = Linker::linkKnown( 00303 SpecialPage::getTitleFor( 'Log', 'block' ), 00304 $this->msg( 'sp-contributions-blocklog' )->escaped(), 00305 array(), 00306 array( 00307 'page' => $userpage->getPrefixedText() 00308 ) 00309 ); 00310 } 00311 # Uploads 00312 $tools[] = Linker::linkKnown( 00313 SpecialPage::getTitleFor( 'Listfiles', $username ), 00314 $this->msg( 'sp-contributions-uploads' )->escaped() 00315 ); 00316 00317 # Other logs link 00318 $tools[] = Linker::linkKnown( 00319 SpecialPage::getTitleFor( 'Log', $username ), 00320 $this->msg( 'sp-contributions-logs' )->escaped() 00321 ); 00322 00323 # Add link to deleted user contributions for priviledged users 00324 if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) { 00325 $tools[] = Linker::linkKnown( 00326 SpecialPage::getTitleFor( 'DeletedContributions', $username ), 00327 $this->msg( 'sp-contributions-deleted' )->escaped() 00328 ); 00329 } 00330 00331 # Add a link to change user rights for privileged users 00332 $userrightsPage = new UserrightsPage(); 00333 $userrightsPage->setContext( $this->getContext() ); 00334 if ( $id !== null && $userrightsPage->userCanChangeRights( $target ) ) { 00335 $tools[] = Linker::linkKnown( 00336 SpecialPage::getTitleFor( 'Userrights', $username ), 00337 $this->msg( 'sp-contributions-userrights' )->escaped() 00338 ); 00339 } 00340 00341 wfRunHooks( 'ContributionsToolLinks', array( $id, $userpage, &$tools ) ); 00342 return $tools; 00343 } 00344 00349 protected function getForm() { 00350 global $wgScript; 00351 00352 $this->opts['title'] = $this->getTitle()->getPrefixedText(); 00353 if ( !isset( $this->opts['target'] ) ) { 00354 $this->opts['target'] = ''; 00355 } else { 00356 $this->opts['target'] = str_replace( '_' , ' ' , $this->opts['target'] ); 00357 } 00358 00359 if ( !isset( $this->opts['namespace'] ) ) { 00360 $this->opts['namespace'] = ''; 00361 } 00362 00363 if ( !isset( $this->opts['nsInvert'] ) ) { 00364 $this->opts['nsInvert'] = ''; 00365 } 00366 00367 if ( !isset( $this->opts['associated'] ) ) { 00368 $this->opts['associated'] = false; 00369 } 00370 00371 if ( !isset( $this->opts['contribs'] ) ) { 00372 $this->opts['contribs'] = 'user'; 00373 } 00374 00375 if ( !isset( $this->opts['year'] ) ) { 00376 $this->opts['year'] = ''; 00377 } 00378 00379 if ( !isset( $this->opts['month'] ) ) { 00380 $this->opts['month'] = ''; 00381 } 00382 00383 if ( $this->opts['contribs'] == 'newbie' ) { 00384 $this->opts['target'] = ''; 00385 } 00386 00387 if ( !isset( $this->opts['tagfilter'] ) ) { 00388 $this->opts['tagfilter'] = ''; 00389 } 00390 00391 if ( !isset( $this->opts['topOnly'] ) ) { 00392 $this->opts['topOnly'] = false; 00393 } 00394 00395 $form = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'class' => 'mw-contributions-form' ) ); 00396 00397 # Add hidden params for tracking except for parameters in $skipParameters 00398 $skipParameters = array( 'namespace', 'nsInvert', 'deletedOnly', 'target', 'contribs', 'year', 'month', 'topOnly', 'associated' ); 00399 foreach ( $this->opts as $name => $value ) { 00400 if ( in_array( $name, $skipParameters ) ) { 00401 continue; 00402 } 00403 $form .= "\t" . Html::hidden( $name, $value ) . "\n"; 00404 } 00405 00406 $tagFilter = ChangeTags::buildTagFilterSelector( $this->opts['tagfilter'] ); 00407 00408 if ( $tagFilter ) { 00409 $filterSelection = 00410 Xml::tags( 'td', array( 'class' => 'mw-label' ), array_shift( $tagFilter ) ) . 00411 Xml::tags( 'td', array( 'class' => 'mw-input' ), implode( ' ', $tagFilter ) ); 00412 } else { 00413 $filterSelection = Xml::tags( 'td', array( 'colspan' => 2 ), '' ); 00414 } 00415 00416 $targetSelection = Xml::tags( 'td', array( 'colspan' => 2 ), 00417 Xml::radioLabel( 00418 $this->msg( 'sp-contributions-newbies' )->text(), 00419 'contribs', 00420 'newbie' , 00421 'newbie', 00422 $this->opts['contribs'] == 'newbie', 00423 array( 'class' => 'mw-input' ) 00424 ) . '<br />' . 00425 Xml::radioLabel( 00426 $this->msg( 'sp-contributions-username' )->text(), 00427 'contribs', 00428 'user', 00429 'user', 00430 $this->opts['contribs'] == 'user', 00431 array( 'class' => 'mw-input' ) 00432 ) . ' ' . 00433 Html::input( 00434 'target', 00435 $this->opts['target'], 00436 'text', 00437 array( 'size' => '20', 'required' => '', 'class' => 'mw-input' ) + 00438 ( $this->opts['target'] ? array() : array( 'autofocus' ) 00439 ) 00440 ) . ' ' 00441 ) ; 00442 00443 $namespaceSelection = 00444 Xml::tags( 'td', array( 'class' => 'mw-label' ), 00445 Xml::label( 00446 $this->msg( 'namespace' )->text(), 00447 'namespace', 00448 '' 00449 ) 00450 ) . 00451 Xml::tags( 'td', null, 00452 Xml::namespaceSelector( $this->opts['namespace'], '' ) . ' ' . 00453 Html::rawElement( 'span', array( 'style' => 'white-space: nowrap' ), 00454 Xml::checkLabel( 00455 $this->msg( 'invert' )->text(), 00456 'nsInvert', 00457 'nsInvert', 00458 $this->opts['nsInvert'], 00459 array( 'title' => $this->msg( 'tooltip-invert' )->text(), 'class' => 'mw-input' ) 00460 ) . ' ' 00461 ) . 00462 Html::rawElement( 'span', array( 'style' => 'white-space: nowrap' ), 00463 Xml::checkLabel( 00464 $this->msg( 'namespace_association' )->text(), 00465 'associated', 00466 'associated', 00467 $this->opts['associated'], 00468 array( 'title' => $this->msg( 'tooltip-namespace_association' )->text(), 'class' => 'mw-input' ) 00469 ) . ' ' 00470 ) 00471 ) ; 00472 00473 $extraOptions = Xml::tags( 'td', array( 'colspan' => 2 ), 00474 Html::rawElement( 'span', array( 'style' => 'white-space: nowrap' ), 00475 Xml::checkLabel( 00476 $this->msg( 'history-show-deleted' )->text(), 00477 'deletedOnly', 00478 'mw-show-deleted-only', 00479 $this->opts['deletedOnly'], 00480 array( 'class' => 'mw-input' ) 00481 ) 00482 ) . 00483 Html::rawElement( 'span', array( 'style' => 'white-space: nowrap' ), 00484 Xml::checkLabel( 00485 $this->msg( 'sp-contributions-toponly' )->text(), 00486 'topOnly', 00487 'mw-show-top-only', 00488 $this->opts['topOnly'], 00489 array( 'class' => 'mw-input' ) 00490 ) 00491 ) 00492 ) ; 00493 00494 $dateSelectionAndSubmit = Xml::tags( 'td', array( 'colspan' => 2 ), 00495 Xml::dateMenu( 00496 $this->opts['year'], 00497 $this->opts['month'] 00498 ) . ' ' . 00499 Xml::submitButton( 00500 $this->msg( 'sp-contributions-submit' )->text(), 00501 array( 'class' => 'mw-submit' ) 00502 ) 00503 ) ; 00504 00505 $form .= 00506 Xml::fieldset( $this->msg( 'sp-contributions-search' )->text() ) . 00507 Xml::openElement( 'table', array( 'class' => 'mw-contributions-table' ) ) . 00508 Xml::openElement( 'tr' ) . 00509 $targetSelection . 00510 Xml::closeElement( 'tr' ) . 00511 Xml::openElement( 'tr' ) . 00512 $namespaceSelection . 00513 Xml::closeElement( 'tr' ) . 00514 Xml::openElement( 'tr' ) . 00515 $filterSelection . 00516 Xml::closeElement( 'tr' ) . 00517 Xml::openElement( 'tr' ) . 00518 $extraOptions . 00519 Xml::closeElement( 'tr' ) . 00520 Xml::openElement( 'tr' ) . 00521 $dateSelectionAndSubmit . 00522 Xml::closeElement( 'tr' ) . 00523 Xml::closeElement( 'table' ); 00524 00525 $explain = $this->msg( 'sp-contributions-explain' ); 00526 if ( $explain->exists() ) { 00527 $form .= "<p id='mw-sp-contributions-explain'>{$explain}</p>"; 00528 } 00529 $form .= Xml::closeElement( 'fieldset' ) . 00530 Xml::closeElement( 'form' ); 00531 return $form; 00532 } 00533 } 00534 00539 class ContribsPager extends ReverseChronologicalPager { 00540 public $mDefaultDirection = true; 00541 var $messages, $target; 00542 var $namespace = '', $mDb; 00543 var $preventClickjacking = false; 00544 00545 function __construct( IContextSource $context, array $options ) { 00546 parent::__construct( $context ); 00547 00548 $msgs = array( 'uctop', 'diff', 'newarticle', 'rollbacklink', 'diff', 'hist', 'rev-delundel', 'pipe-separator' ); 00549 00550 foreach ( $msgs as $msg ) { 00551 $this->messages[$msg] = $this->msg( $msg )->escaped(); 00552 } 00553 00554 $this->target = isset( $options['target'] ) ? $options['target'] : ''; 00555 $this->contribs = isset( $options['contribs'] ) ? $options['contribs'] : 'users'; 00556 $this->namespace = isset( $options['namespace'] ) ? $options['namespace'] : ''; 00557 $this->tagFilter = isset( $options['tagfilter'] ) ? $options['tagfilter'] : false; 00558 $this->nsInvert = isset( $options['nsInvert'] ) ? $options['nsInvert'] : false; 00559 $this->associated = isset( $options['associated'] ) ? $options['associated'] : false; 00560 00561 $this->deletedOnly = !empty( $options['deletedOnly'] ); 00562 $this->topOnly = !empty( $options['topOnly'] ); 00563 00564 $year = isset( $options['year'] ) ? $options['year'] : false; 00565 $month = isset( $options['month'] ) ? $options['month'] : false; 00566 $this->getDateCond( $year, $month ); 00567 00568 $this->mDb = wfGetDB( DB_SLAVE, 'contributions' ); 00569 } 00570 00571 function getDefaultQuery() { 00572 $query = parent::getDefaultQuery(); 00573 $query['target'] = $this->target; 00574 return $query; 00575 } 00576 00577 function getQueryInfo() { 00578 list( $tables, $index, $userCond, $join_cond ) = $this->getUserCond(); 00579 00580 $user = $this->getUser(); 00581 $conds = array_merge( $userCond, $this->getNamespaceCond() ); 00582 00583 // Paranoia: avoid brute force searches (bug 17342) 00584 if ( !$user->isAllowed( 'deletedhistory' ) ) { 00585 $conds[] = $this->mDb->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0'; 00586 } elseif ( !$user->isAllowed( 'suppressrevision' ) ) { 00587 $conds[] = $this->mDb->bitAnd( 'rev_deleted', Revision::SUPPRESSED_USER ) . 00588 ' != ' . Revision::SUPPRESSED_USER; 00589 } 00590 00591 # Don't include orphaned revisions 00592 $join_cond['page'] = Revision::pageJoinCond(); 00593 # Get the current user name for accounts 00594 $join_cond['user'] = Revision::userJoinCond(); 00595 00596 $queryInfo = array( 00597 'tables' => $tables, 00598 'fields' => array_merge( 00599 Revision::selectFields(), 00600 Revision::selectUserFields(), 00601 array( 'page_namespace', 'page_title', 'page_is_new', 00602 'page_latest', 'page_is_redirect', 'page_len' ) 00603 ), 00604 'conds' => $conds, 00605 'options' => array( 'USE INDEX' => array( 'revision' => $index ) ), 00606 'join_conds' => $join_cond 00607 ); 00608 00609 ChangeTags::modifyDisplayQuery( 00610 $queryInfo['tables'], 00611 $queryInfo['fields'], 00612 $queryInfo['conds'], 00613 $queryInfo['join_conds'], 00614 $queryInfo['options'], 00615 $this->tagFilter 00616 ); 00617 00618 wfRunHooks( 'ContribsPager::getQueryInfo', array( &$this, &$queryInfo ) ); 00619 return $queryInfo; 00620 } 00621 00622 function getUserCond() { 00623 $condition = array(); 00624 $join_conds = array(); 00625 $tables = array( 'revision', 'page', 'user' ); 00626 if ( $this->contribs == 'newbie' ) { 00627 $tables[] = 'user_groups'; 00628 $max = $this->mDb->selectField( 'user', 'max(user_id)', false, __METHOD__ ); 00629 $condition[] = 'rev_user >' . (int)( $max - $max / 100 ); 00630 $condition[] = 'ug_group IS NULL'; 00631 $index = 'user_timestamp'; 00632 # @todo FIXME: Other groups may have 'bot' rights 00633 $join_conds['user_groups'] = array( 'LEFT JOIN', "ug_user = rev_user AND ug_group = 'bot'" ); 00634 } else { 00635 if ( IP::isIPAddress( $this->target ) ) { 00636 $condition['rev_user_text'] = $this->target; 00637 $index = 'usertext_timestamp'; 00638 } else { 00639 $condition['rev_user'] = User::idFromName( $this->target ); 00640 $index = 'user_timestamp'; 00641 } 00642 } 00643 if ( $this->deletedOnly ) { 00644 $condition[] = "rev_deleted != '0'"; 00645 } 00646 if ( $this->topOnly ) { 00647 $condition[] = "rev_id = page_latest"; 00648 } 00649 return array( $tables, $index, $condition, $join_conds ); 00650 } 00651 00652 function getNamespaceCond() { 00653 if ( $this->namespace !== '' ) { 00654 $selectedNS = $this->mDb->addQuotes( $this->namespace ); 00655 $eq_op = $this->nsInvert ? '!=' : '='; 00656 $bool_op = $this->nsInvert ? 'AND' : 'OR'; 00657 00658 if ( !$this->associated ) { 00659 return array( "page_namespace $eq_op $selectedNS" ); 00660 } else { 00661 $associatedNS = $this->mDb->addQuotes ( 00662 MWNamespace::getAssociated( $this->namespace ) 00663 ); 00664 return array( 00665 "page_namespace $eq_op $selectedNS " . 00666 $bool_op . 00667 " page_namespace $eq_op $associatedNS" 00668 ); 00669 } 00670 00671 } else { 00672 return array(); 00673 } 00674 } 00675 00676 function getIndexField() { 00677 return 'rev_timestamp'; 00678 } 00679 00680 function doBatchLookups() { 00681 $this->mResult->rewind(); 00682 $revIds = array(); 00683 foreach ( $this->mResult as $row ) { 00684 if( $row->rev_parent_id ) { 00685 $revIds[] = $row->rev_parent_id; 00686 } 00687 } 00688 $this->mParentLens = $this->getParentLengths( $revIds ); 00689 $this->mResult->rewind(); // reset 00690 00691 # Do a link batch query 00692 $this->mResult->seek( 0 ); 00693 $batch = new LinkBatch(); 00694 # Give some pointers to make (last) links 00695 foreach ( $this->mResult as $row ) { 00696 if ( $this->contribs === 'newbie' ) { // multiple users 00697 $batch->add( NS_USER, $row->user_name ); 00698 $batch->add( NS_USER_TALK, $row->user_name ); 00699 } 00700 $batch->add( $row->page_namespace, $row->page_title ); 00701 } 00702 $batch->execute(); 00703 $this->mResult->seek( 0 ); 00704 } 00705 00709 private function getParentLengths( array $revIds ) { 00710 $revLens = array(); 00711 if ( !$revIds ) { 00712 return $revLens; // empty 00713 } 00714 wfProfileIn( __METHOD__ ); 00715 $res = $this->getDatabase()->select( 'revision', 00716 array( 'rev_id', 'rev_len' ), 00717 array( 'rev_id' => $revIds ), 00718 __METHOD__ ); 00719 foreach ( $res as $row ) { 00720 $revLens[$row->rev_id] = $row->rev_len; 00721 } 00722 wfProfileOut( __METHOD__ ); 00723 return $revLens; 00724 } 00725 00729 function getStartBody() { 00730 return "<ul>\n"; 00731 } 00732 00736 function getEndBody() { 00737 return "</ul>\n"; 00738 } 00739 00750 function formatRow( $row ) { 00751 wfProfileIn( __METHOD__ ); 00752 00753 $rev = new Revision( $row ); 00754 $classes = array(); 00755 00756 $page = Title::newFromRow( $row ); 00757 $link = Linker::link( 00758 $page, 00759 htmlspecialchars( $page->getPrefixedText() ), 00760 array(), 00761 $page->isRedirect() ? array( 'redirect' => 'no' ) : array() 00762 ); 00763 # Mark current revisions 00764 $topmarktext = ''; 00765 if ( $row->rev_id == $row->page_latest ) { 00766 $topmarktext .= '<span class="mw-uctop">' . $this->messages['uctop'] . '</span>'; 00767 # Add rollback link 00768 if ( !$row->page_is_new && $page->quickUserCan( 'rollback' ) 00769 && $page->quickUserCan( 'edit' ) ) 00770 { 00771 $this->preventClickjacking(); 00772 $topmarktext .= ' ' . Linker::generateRollback( $rev ); 00773 } 00774 } 00775 $user = $this->getUser(); 00776 # Is there a visible previous revision? 00777 if ( $rev->userCan( Revision::DELETED_TEXT, $user ) && $rev->getParentId() !== 0 ) { 00778 $difftext = Linker::linkKnown( 00779 $page, 00780 $this->messages['diff'], 00781 array(), 00782 array( 00783 'diff' => 'prev', 00784 'oldid' => $row->rev_id 00785 ) 00786 ); 00787 } else { 00788 $difftext = $this->messages['diff']; 00789 } 00790 $histlink = Linker::linkKnown( 00791 $page, 00792 $this->messages['hist'], 00793 array(), 00794 array( 'action' => 'history' ) 00795 ); 00796 00797 if ( $row->rev_parent_id === null ) { 00798 // For some reason rev_parent_id isn't populated for this row. 00799 // Its rumoured this is true on wikipedia for some revisions (bug 34922). 00800 // Next best thing is to have the total number of bytes. 00801 $chardiff = ' . . ' . Linker::formatRevisionSize( $row->rev_len ) . ' . . '; 00802 } else { 00803 $parentLen = isset( $this->mParentLens[$row->rev_parent_id] ) ? $this->mParentLens[$row->rev_parent_id] : 0; 00804 $chardiff = ' . . ' . ChangesList::showCharacterDifference( 00805 $parentLen, $row->rev_len ) . ' . . '; 00806 } 00807 00808 $lang = $this->getLanguage(); 00809 $comment = $lang->getDirMark() . Linker::revComment( $rev, false, true ); 00810 $date = $lang->userTimeAndDate( $row->rev_timestamp, $user ); 00811 if ( $rev->userCan( Revision::DELETED_TEXT, $user ) ) { 00812 $d = Linker::linkKnown( 00813 $page, 00814 htmlspecialchars( $date ), 00815 array(), 00816 array( 'oldid' => intval( $row->rev_id ) ) 00817 ); 00818 } else { 00819 $d = htmlspecialchars( $date ); 00820 } 00821 if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) { 00822 $d = '<span class="history-deleted">' . $d . '</span>'; 00823 } 00824 00825 # Show user names for /newbies as there may be different users. 00826 # Note that we already excluded rows with hidden user names. 00827 if ( $this->contribs == 'newbie' ) { 00828 $userlink = ' . . ' . Linker::userLink( $rev->getUser(), $rev->getUserText() ); 00829 $userlink .= ' ' . $this->msg( 'parentheses' )->rawParams( 00830 Linker::userTalkLink( $rev->getUser(), $rev->getUserText() ) )->escaped() . ' '; 00831 } else { 00832 $userlink = ''; 00833 } 00834 00835 if ( $rev->getParentId() === 0 ) { 00836 $nflag = ChangesList::flag( 'newpage' ); 00837 } else { 00838 $nflag = ''; 00839 } 00840 00841 if ( $rev->isMinor() ) { 00842 $mflag = ChangesList::flag( 'minor' ); 00843 } else { 00844 $mflag = ''; 00845 } 00846 00847 $del = Linker::getRevDeleteLink( $user, $rev, $page ); 00848 if ( $del !== '' ) { 00849 $del .= ' '; 00850 } 00851 00852 $diffHistLinks = '(' . $difftext . $this->messages['pipe-separator'] . $histlink . ')'; 00853 $ret = "{$del}{$d} {$diffHistLinks}{$chardiff}{$nflag}{$mflag} {$link}{$userlink} {$comment} {$topmarktext}"; 00854 00855 # Denote if username is redacted for this edit 00856 if ( $rev->isDeleted( Revision::DELETED_USER ) ) { 00857 $ret .= " <strong>" . $this->msg( 'rev-deleted-user-contribs' )->escaped() . "</strong>"; 00858 } 00859 00860 # Tags, if any. 00861 list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow( $row->ts_tags, 'contributions' ); 00862 $classes = array_merge( $classes, $newClasses ); 00863 $ret .= " $tagSummary"; 00864 00865 // Let extensions add data 00866 wfRunHooks( 'ContributionsLineEnding', array( &$this, &$ret, $row ) ); 00867 00868 $classes = implode( ' ', $classes ); 00869 $ret = "<li class=\"$classes\">$ret</li>\n"; 00870 wfProfileOut( __METHOD__ ); 00871 return $ret; 00872 } 00873 00879 public function getDatabase() { 00880 return $this->mDb; 00881 } 00882 00886 function getSqlComment() { 00887 if ( $this->namespace || $this->deletedOnly ) { 00888 return 'contributions page filtered for namespace or RevisionDeleted edits'; // potentially slow, see CR r58153 00889 } else { 00890 return 'contributions page unfiltered'; 00891 } 00892 } 00893 00894 protected function preventClickjacking() { 00895 $this->preventClickjacking = true; 00896 } 00897 00898 public function getPreventClickjacking() { 00899 return $this->preventClickjacking; 00900 } 00901 }