00001 <?php
00008 class SpecialContributions extends SpecialPage {
00009
00010 public function __construct() {
00011 parent::__construct( 'Contributions' );
00012 }
00013
00014 public function execute( $par ) {
00015 global $wgUser, $wgOut, $wgLang, $wgRequest;
00016
00017 $this->setHeaders();
00018 $this->outputHeader();
00019
00020 $this->opts = array();
00021
00022 if( $par == 'newbies' ) {
00023 $target = 'newbies';
00024 $this->opts['contribs'] = 'newbie';
00025 } elseif( isset( $par ) ) {
00026 $target = $par;
00027 } else {
00028 $target = $wgRequest->getVal( 'target' );
00029 }
00030
00031
00032 if( $wgRequest->getVal( 'contribs' ) == 'newbie' ) {
00033 $target = 'newbies';
00034 $this->opts['contribs'] = 'newbie';
00035 }
00036
00037 if( !strlen( $target ) ) {
00038 $wgOut->addHTML( $this->getForm() );
00039 return;
00040 }
00041
00042 $this->opts['limit'] = $wgRequest->getInt( 'limit', 50 );
00043 $this->opts['target'] = $target;
00044
00045 $nt = Title::makeTitleSafe( NS_USER, $target );
00046 if( !$nt ) {
00047 $wgOut->addHTML( $this->getForm() );
00048 return;
00049 }
00050 $id = User::idFromName( $nt->getText() );
00051
00052 if( $target != 'newbies' ) {
00053 $target = $nt->getText();
00054 $wgOut->setSubtitle( $this->contributionsSub( $nt, $id ) );
00055 $wgOut->setHTMLTitle( wfMsg( 'pagetitle', wfMsgExt( 'contributions-title', array( 'parsemag' ),$target ) ) );
00056 } else {
00057 $wgOut->setSubtitle( wfMsgHtml( 'sp-contributions-newbies-sub') );
00058 $wgOut->setHTMLTitle( wfMsg( 'pagetitle', wfMsg( 'sp-contributions-newbies-title' ) ) );
00059 }
00060
00061 if( ( $ns = $wgRequest->getVal( 'namespace', null ) ) !== null && $ns !== '' ) {
00062 $this->opts['namespace'] = intval( $ns );
00063 } else {
00064 $this->opts['namespace'] = '';
00065 }
00066
00067 $this->opts['tagfilter'] = (string) $wgRequest->getVal( 'tagfilter' );
00068
00069
00070
00071 if( $wgUser->isAllowed( 'markbotedits' ) && $wgRequest->getBool( 'bot' ) ) {
00072 $this->opts['bot'] = '1';
00073 }
00074
00075 $skip = $wgRequest->getText( 'offset' ) || $wgRequest->getText( 'dir' ) == 'prev';
00076 # Offset overrides year/month selection
00077 if( $skip ) {
00078 $this->opts['year'] = '';
00079 $this->opts['month'] = '';
00080 } else {
00081 $this->opts['year'] = $wgRequest->getIntOrNull( 'year' );
00082 $this->opts['month'] = $wgRequest->getIntOrNull( 'month' );
00083 }
00084
00085
00086 $this->setSyndicated();
00087 $feedType = $wgRequest->getVal( 'feed' );
00088 if( $feedType ) {
00089 return $this->feed( $feedType );
00090 }
00091
00092 wfRunHooks( 'SpecialContributionsBeforeMainOutput', $id );
00093
00094 $wgOut->addHTML( $this->getForm() );
00095
00096 $pager = new ContribsPager( $target, $this->opts['namespace'], $this->opts['year'], $this->opts['month'] );
00097 if( !$pager->getNumRows() ) {
00098 $wgOut->addWikiMsg( 'nocontribs', $target );
00099 return;
00100 }
00101
00102 # Show a message about slave lag, if applicable
00103 if( ( $lag = $pager->getDatabase()->getLag() ) > 0 )
00104 $wgOut->showLagWarning( $lag );
00105
00106 $wgOut->addHTML(
00107 '<p>' . $pager->getNavigationBar() . '</p>' .
00108 $pager->getBody() .
00109 '<p>' . $pager->getNavigationBar() . '</p>'
00110 );
00111
00112 # If there were contributions, and it was a valid user or IP, show
00113 # the appropriate "footer" message - WHOIS tools, etc.
00114 if( $target != 'newbies' ) {
00115 $message = IP::isIPAddress( $target ) ?
00116 'sp-contributions-footer-anon' : 'sp-contributions-footer';
00117
00118 $text = wfMsgNoTrans( $message, $target );
00119 if( !wfEmptyMsg( $message, $text ) && $text != '-' ) {
00120 $wgOut->addHTML( '<div class="mw-contributions-footer">' );
00121 $wgOut->addWikiText( $text );
00122 $wgOut->addHTML( '</div>' );
00123 }
00124 }
00125 }
00126
00127 protected function setSyndicated() {
00128 global $wgOut;
00129 $queryParams = array(
00130 'namespace' => $this->opts['namespace'],
00131 'target' => $this->opts['target']
00132 );
00133 $wgOut->setSyndicated( true );
00134 $wgOut->setFeedAppendQuery( wfArrayToCGI( $queryParams ) );
00135 }
00136
00143 protected function contributionsSub( $nt, $id ) {
00144 global $wgSysopUserBans, $wgLang, $wgUser;
00145
00146 $sk = $wgUser->getSkin();
00147
00148 if( 0 == $id ) {
00149 $user = $nt->getText();
00150 } else {
00151 $user = $sk->makeLinkObj( $nt, htmlspecialchars( $nt->getText() ) );
00152 }
00153 $talk = $nt->getTalkPage();
00154 if( $talk ) {
00155 # Talk page link
00156 $tools[] = $sk->makeLinkObj( $talk, wfMsgHtml( 'talkpagelinktext' ) );
00157 if( ( $id != 0 && $wgSysopUserBans ) || ( $id == 0 && IP::isIPAddress( $nt->getText() ) ) ) {
00158 # Block link
00159 if( $wgUser->isAllowed( 'block' ) )
00160 $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Blockip',
00161 $nt->getDBkey() ), wfMsgHtml( 'blocklink' ) );
00162 # Block log link
00163 $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ),
00164 wfMsgHtml( 'sp-contributions-blocklog' ), 'type=block&page=' . $nt->getPrefixedUrl() );
00165 }
00166 # Other logs link
00167 $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), wfMsg( 'sp-contributions-logs' ),
00168 'user=' . $nt->getPartialUrl() );
00169
00170 # Add link to deleted user contributions for priviledged users
00171 if( $wgUser->isAllowed( 'deletedhistory' ) ) {
00172 $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'DeletedContributions',
00173 $nt->getDBkey() ), wfMsgHtml( 'deletedcontributions' ) );
00174 }
00175
00176 # Add a link to change user rights for privileged users
00177 $userrightsPage = new UserrightsPage();
00178 if( 0 !== $id && $userrightsPage->userCanChangeRights( User::newFromId( $id ) ) ) {
00179 $tools[] = $sk->makeKnownLinkObj(
00180 SpecialPage::getTitleFor( 'Userrights', $nt->getDBkey() ),
00181 wfMsgHtml( 'userrights' )
00182 );
00183 }
00184
00185 wfRunHooks( 'ContributionsToolLinks', array( $id, $nt, &$tools ) );
00186
00187 $links = $wgLang->pipeList( $tools );
00188 }
00189
00190
00191
00192
00193
00194 if( wfEmptyMsg( 'contribsub', wfMsg( 'contribsub' ) ) ) {
00195 return wfMsgHtml( 'contribsub2', $user, $links );
00196 } else {
00197 return wfMsgHtml( 'contribsub', "$user ($links)" );
00198 }
00199 }
00200
00205 protected function getForm() {
00206 global $wgScript, $wgTitle;
00207
00208 $this->opts['title'] = $wgTitle->getPrefixedText();
00209 if( !isset( $this->opts['target'] ) ) {
00210 $this->opts['target'] = '';
00211 } else {
00212 $this->opts['target'] = str_replace( '_' , ' ' , $this->opts['target'] );
00213 }
00214
00215 if( !isset( $this->opts['namespace'] ) ) {
00216 $this->opts['namespace'] = '';
00217 }
00218
00219 if( !isset( $this->opts['contribs'] ) ) {
00220 $this->opts['contribs'] = 'user';
00221 }
00222
00223 if( !isset( $this->opts['year'] ) ) {
00224 $this->opts['year'] = '';
00225 }
00226
00227 if( !isset( $this->opts['month'] ) ) {
00228 $this->opts['month'] = '';
00229 }
00230
00231 if( $this->opts['contribs'] == 'newbie' ) {
00232 $this->opts['target'] = '';
00233 }
00234
00235 if( !isset( $this->opts['tagfilter'] ) ) {
00236 $this->opts['tagfilter'] = '';
00237 }
00238
00239 $f = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
00240 # Add hidden params for tracking
00241 foreach ( $this->opts as $name => $value ) {
00242 if( in_array( $name, array( 'namespace', 'target', 'contribs', 'year', 'month' ) ) ) {
00243 continue;
00244 }
00245 $f .= "\t" . Xml::hidden( $name, $value ) . "\n";
00246 }
00247
00248 $tagFilter = ChangeTags::buildTagFilterSelector( $this->opts['tagfilter'] );
00249
00250 $f .= '<fieldset>' .
00251 Xml::element( 'legend', array(), wfMsg( 'sp-contributions-search' ) ) .
00252 Xml::radioLabel( wfMsgExt( 'sp-contributions-newbies', array( 'parseinline' ) ),
00253 'contribs', 'newbie' , 'newbie', $this->opts['contribs'] == 'newbie' ? true : false ) . '<br />' .
00254 Xml::radioLabel( wfMsgExt( 'sp-contributions-username', array( 'parseinline' ) ),
00255 'contribs' , 'user', 'user', $this->opts['contribs'] == 'user' ? true : false ) . ' ' .
00256 Xml::input( 'target', 20, $this->opts['target']) . ' '.
00257 '<span style="white-space: nowrap">' .
00258 Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' .
00259 Xml::namespaceSelector( $this->opts['namespace'], '' ) .
00260 '</span>' .
00261 ( $tagFilter ? Xml::tags( 'p', null, implode( ' ', $tagFilter ) ) : '' ) .
00262 Xml::openElement( 'p' ) .
00263 '<span style="white-space: nowrap">' .
00264 Xml::dateMenu( $this->opts['year'], $this->opts['month'] ) .
00265 '</span>' . ' ' .
00266 Xml::submitButton( wfMsg( 'sp-contributions-submit' ) ) .
00267 Xml::closeElement( 'p' );
00268
00269 $explain = wfMsgExt( 'sp-contributions-explain', 'parseinline' );
00270 if( !wfEmptyMsg( 'sp-contributions-explain', $explain ) )
00271 $f .= "<p>{$explain}</p>";
00272
00273 $f .= '</fieldset>' .
00274 Xml::closeElement( 'form' );
00275 return $f;
00276 }
00277
00282 protected function feed( $type ) {
00283 global $wgRequest, $wgFeed, $wgFeedClasses, $wgFeedLimit;
00284
00285 if( !$wgFeed ) {
00286 global $wgOut;
00287 $wgOut->addWikiMsg( 'feed-unavailable' );
00288 return;
00289 }
00290
00291 if( !isset( $wgFeedClasses[$type] ) ) {
00292 global $wgOut;
00293 $wgOut->addWikiMsg( 'feed-invalid' );
00294 return;
00295 }
00296
00297 $feed = new $wgFeedClasses[$type](
00298 $this->feedTitle(),
00299 wfMsgExt( 'tagline', 'parsemag' ),
00300 $this->getTitle()->getFullUrl() . "/" . urlencode($this->opts['target'])
00301 );
00302
00303
00304 $nt = Title::makeTitleSafe( NS_USER, $this->opts['target'] );
00305 $target = $this->opts['target'] == 'newbies' ? 'newbies' : $nt->getText();
00306
00307 $pager = new ContribsPager( $target, $this->opts['namespace'],
00308 $this->opts['year'], $this->opts['month'], $this->opts['tagfilter'] );
00309
00310 $pager->mLimit = min( $this->opts['limit'], $wgFeedLimit );
00311
00312 $feed->outHeader();
00313 if( $pager->getNumRows() > 0 ) {
00314 while( $row = $pager->mResult->fetchObject() ) {
00315 $feed->outItem( $this->feedItem( $row ) );
00316 }
00317 }
00318 $feed->outFooter();
00319 }
00320
00321 protected function feedTitle() {
00322 global $wgContLanguageCode, $wgSitename;
00323 $page = SpecialPage::getPage( 'Contributions' );
00324 $desc = $page->getDescription();
00325 return "$wgSitename - $desc [$wgContLanguageCode]";
00326 }
00327
00328 protected function feedItem( $row ) {
00329 $title = Title::MakeTitle( intval( $row->page_namespace ), $row->page_title );
00330 if( $title ) {
00331 $date = $row->rev_timestamp;
00332 $comments = $title->getTalkPage()->getFullURL();
00333 $revision = Revision::newFromTitle( $title, $row->rev_id );
00334
00335 return new FeedItem(
00336 $title->getPrefixedText(),
00337 $this->feedItemDesc( $revision ),
00338 $title->getFullURL(),
00339 $date,
00340 $this->feedItemAuthor( $revision ),
00341 $comments
00342 );
00343 } else {
00344 return NULL;
00345 }
00346 }
00347
00348 protected function feedItemAuthor( $revision ) {
00349 return $revision->getUserText();
00350 }
00351
00352 protected function feedItemDesc( $revision ) {
00353 if( $revision ) {
00354 return '<p>' . htmlspecialchars( $revision->getUserText() ) . wfMsgForContent( 'colon-separator' ) .
00355 htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) .
00356 "</p>\n<hr />\n<div>" .
00357 nl2br( htmlspecialchars( $revision->getText() ) ) . "</div>";
00358 }
00359 return '';
00360 }
00361 }
00362
00367 class ContribsPager extends ReverseChronologicalPager {
00368 public $mDefaultDirection = true;
00369 var $messages, $target;
00370 var $namespace = '', $mDb;
00371
00372 function __construct( $target, $namespace = false, $year = false, $month = false, $tagFilter = false ) {
00373 parent::__construct();
00374 foreach( explode( ' ', 'uctop diff newarticle rollbacklink diff hist newpageletter minoreditletter' ) as $msg ) {
00375 $this->messages[$msg] = wfMsgExt( $msg, array( 'escape') );
00376 }
00377 $this->target = $target;
00378 $this->namespace = $namespace;
00379 $this->tagFilter = $tagFilter;
00380
00381 $this->getDateCond( $year, $month );
00382
00383 $this->mDb = wfGetDB( DB_SLAVE, 'contributions' );
00384 }
00385
00386 function getDefaultQuery() {
00387 $query = parent::getDefaultQuery();
00388 $query['target'] = $this->target;
00389 return $query;
00390 }
00391
00392 function getQueryInfo() {
00393 global $wgUser;
00394 list( $tables, $index, $userCond, $join_cond ) = $this->getUserCond();
00395
00396 $conds = array_merge( $userCond, $this->getNamespaceCond() );
00397
00398 if( !$wgUser->isAllowed( 'suppressrevision' ) ) {
00399 $conds[] = 'rev_deleted & ' . Revision::DELETED_USER . ' = 0';
00400 }
00401 $join_cond['page'] = array( 'INNER JOIN', 'page_id=rev_page' );
00402
00403 $queryInfo = array(
00404 'tables' => $tables,
00405 'fields' => array(
00406 'page_namespace', 'page_title', 'page_is_new', 'page_latest', 'page_is_redirect',
00407 'page_len','rev_id', 'rev_page', 'rev_text_id', 'rev_timestamp', 'rev_comment',
00408 'rev_minor_edit', 'rev_user', 'rev_user_text', 'rev_parent_id', 'rev_deleted'
00409 ),
00410 'conds' => $conds,
00411 'options' => array( 'USE INDEX' => array('revision' => $index) ),
00412 'join_conds' => $join_cond
00413 );
00414
00415 ChangeTags::modifyDisplayQuery( $queryInfo['tables'],
00416 $queryInfo['fields'],
00417 $queryInfo['conds'],
00418 $queryInfo['join_conds'],
00419 $queryInfo['options'],
00420 $this->tagFilter );
00421
00422 wfRunHooks( 'ContribsPager::getQueryInfo', array( &$this, &$queryInfo ) );
00423 return $queryInfo;
00424 }
00425
00426 function getUserCond() {
00427 $condition = array();
00428 $join_conds = array();
00429 if( $this->target == 'newbies' ) {
00430 $tables = array( 'user_groups', 'page', 'revision' );
00431 $max = $this->mDb->selectField( 'user', 'max(user_id)', false, __METHOD__ );
00432 $condition[] = 'rev_user >' . (int)($max - $max / 100);
00433 $condition[] = 'ug_group IS NULL';
00434 $index = 'user_timestamp';
00435 # FIXME: other groups may have 'bot' rights
00436 $join_conds['user_groups'] = array( 'LEFT JOIN', "ug_user = rev_user AND ug_group = 'bot'" );
00437 } else {
00438 $tables = array( 'page', 'revision' );
00439 $condition['rev_user_text'] = $this->target;
00440 $index = 'usertext_timestamp';
00441 }
00442 return array( $tables, $index, $condition, $join_conds );
00443 }
00444
00445 function getNamespaceCond() {
00446 if( $this->namespace !== '' ) {
00447 return array( 'page_namespace' => (int)$this->namespace );
00448 } else {
00449 return array();
00450 }
00451 }
00452
00453 function getIndexField() {
00454 return 'rev_timestamp';
00455 }
00456
00457 function getStartBody() {
00458 return "<ul>\n";
00459 }
00460
00461 function getEndBody() {
00462 return "</ul>\n";
00463 }
00464
00475 function formatRow( $row ) {
00476 global $wgLang, $wgUser, $wgContLang;
00477 wfProfileIn( __METHOD__ );
00478
00479 $sk = $this->getSkin();
00480 $rev = new Revision( $row );
00481 $classes = array();
00482
00483 $page = Title::newFromRow( $row );
00484 $page->resetArticleId( $row->rev_page );
00485 $link = $sk->makeLinkObj( $page, $page->getPrefixedText(), $page->isRedirect() ? 'redirect=no' : '' );
00486 # Mark current revisions
00487 $difftext = $topmarktext = '';
00488 if( $row->rev_id == $row->page_latest ) {
00489 $topmarktext .= '<strong>' . $this->messages['uctop'] . '</strong>';
00490 if( !$row->page_is_new ) {
00491 $difftext .= '(' . $sk->makeKnownLinkObj( $page, $this->messages['diff'], 'diff=0' ) . ')';
00492 # Add rollback link
00493 if( $page->quickUserCan( 'rollback') && $page->quickUserCan( 'edit' ) ) {
00494 $topmarktext .= ' '.$sk->generateRollback( $rev );
00495 }
00496 } else {
00497 $difftext .= $this->messages['newarticle'];
00498 }
00499 }
00500 # Is there a visible previous revision?
00501 if( $rev->userCan(Revision::DELETED_TEXT) ) {
00502 $difftext = '(' . $sk->makeKnownLinkObj( $page, $this->messages['diff'],
00503 'diff=prev&oldid='.$row->rev_id ) . ')';
00504 } else {
00505 $difftext = '(' . $this->messages['diff'] . ')';
00506 }
00507 $histlink = '('.$sk->makeKnownLinkObj( $page, $this->messages['hist'], 'action=history' ) . ')';
00508
00509 $comment = $wgContLang->getDirMark() . $sk->revComment( $rev, false, true );
00510 $date = $wgLang->timeanddate( wfTimestamp( TS_MW, $row->rev_timestamp ), true );
00511 $d = $sk->makeKnownLinkObj( $page, $date, 'oldid='.intval($row->rev_id) );
00512
00513 if( $this->target == 'newbies' ) {
00514 $userlink = ' . . ' . $sk->userLink( $row->rev_user, $row->rev_user_text );
00515 $userlink .= ' (' . $sk->userTalkLink( $row->rev_user, $row->rev_user_text ) . ') ';
00516 } else {
00517 $userlink = '';
00518 }
00519
00520 if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
00521 $d = '<span class="history-deleted">' . $d . '</span>';
00522 }
00523
00524 if( $rev->getParentId() === 0 ) {
00525 $nflag = '<span class="newpage">' . $this->messages['newpageletter'] . '</span>';
00526 } else {
00527 $nflag = '';
00528 }
00529
00530 if( $rev->isMinor() ) {
00531 $mflag = '<span class="minor">' . $this->messages['minoreditletter'] . '</span> ';
00532 } else {
00533 $mflag = '';
00534 }
00535
00536 $ret = "{$d} {$histlink} {$difftext} {$nflag}{$mflag} {$link}{$userlink} {$comment} {$topmarktext}";
00537 if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
00538 $ret .= ' ' . wfMsgHtml( 'deletedrev' );
00539 }
00540
00541 # Tags, if any.
00542 list($tagSummary, $newClasses) = ChangeTags::formatSummaryRow( $row->ts_tags, 'contributions' );
00543 $classes = array_merge( $classes, $newClasses );
00544 $ret .= " $tagSummary";
00545
00546
00547 wfRunHooks( 'ContributionsLineEnding', array( &$this, &$ret, $row ) );
00548
00549 $classes = implode( ' ', $classes );
00550 $ret = "<li class=\"$classes\">$ret</li>\n";
00551 wfProfileOut( __METHOD__ );
00552 return $ret;
00553 }
00554
00560 public function getDatabase() {
00561 return $this->mDb;
00562 }
00563
00564 }