X-Git-Url: http://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FSpecialContributions.php;h=9ba433a751999181afcd7fd137181045ff8d053c;hb=98c66071a35d14a1e396e248796295f91bacf7e9;hp=0a142d79a65982457cf77ea6951f960411b17dcb;hpb=96e1de79676f39ef96eb862195700b255363ef74;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/SpecialContributions.php b/includes/SpecialContributions.php index 0a142d79a6..9ba433a751 100644 --- a/includes/SpecialContributions.php +++ b/includes/SpecialContributions.php @@ -1,336 +1,441 @@ user = User::newFromName( $username, false ); - } +class ContribsPager extends ReverseChronologicalPager { + public $mDefaultDirection = true; + var $messages, $target; + var $namespace = '', $year = '', $month = '', $mDb; - /** - * @return string Name of this special page. - */ - function getName() { - return 'Contributions'; + function __construct( $target, $namespace = false, $year = false, $month = false ) { + parent::__construct(); + foreach( explode( ' ', 'uctop diff newarticle rollbacklink diff hist newpageletter minoreditletter' ) as $msg ) { + $this->messages[$msg] = wfMsgExt( $msg, array( 'escape') ); + } + $this->target = $target; + $this->namespace = $namespace; + + $year = intval($year); + $month = intval($month); + + $this->year = ($year > 0 && $year < 10000) ? $year : false; + $this->month = ($month > 0 && $month < 13) ? $month : false; + $this->getDateCond(); + + $this->mDb = wfGetDB( DB_SLAVE, 'contributions' ); } - /** - * Not expensive, won't work with the query cache anyway. - */ - function isExpensive() { return false; } - - /** - * Should we? - */ - function isSyndicated() { return false; } - - /** - * Get target user name. May be overridden in subclasses. - * @return string username - */ - function getUsername() { - return $this->user->getName(); + function getDefaultQuery() { + $query = parent::getDefaultQuery(); + $query['target'] = $this->target; + $query['month'] = $this->month; + $query['year'] = $this->year; + return $query; } - /** - * @return array Extra URL params for self-links. - */ - function linkParameters() { - $params['target'] = $this->getUsername(); - - if ( isset($this->namespace) ) - $params['namespace'] = $this->namespace; - - if ( $this->botmode ) - $params['bot'] = 1; - - return $params; + function getQueryInfo() { + list( $index, $userCond ) = $this->getUserCond(); + $conds = array_merge( array('page_id=rev_page'), $userCond, $this->getNamespaceCond() ); + + return array( + 'tables' => array( 'page', 'revision' ), + 'fields' => array( + 'page_namespace', 'page_title', 'page_is_new', 'page_latest', 'rev_id', 'rev_page', + 'rev_text_id', 'rev_timestamp', 'rev_comment', 'rev_minor_edit', 'rev_user', + 'rev_user_text', 'rev_parent_id', 'rev_deleted' + ), + 'conds' => $conds, + 'options' => array( 'USE INDEX' => $index ) + ); } - /** - * Build the list of links to be shown in the subtitle. - * @return string Link list for "contribsub" UI message. - */ - function getTargetUserLinks() { - global $wgSysopUserBans, $wgLang, $wgUser; - - $skin = $wgUser->getSkin(); + function getUserCond() { + $condition = array(); - $username = $this->getUsername(); - $userpage = $this->user->getUserPage(); - $userlink = $skin->makeLinkObj( $userpage, $username ); - - // talk page link - $tools[] = $skin->makeLinkObj( $userpage->getTalkPage(), $wgLang->getNsText( NS_TALK ) ); + if ( $this->target == 'newbies' ) { + $max = $this->mDb->selectField( 'user', 'max(user_id)', false, __METHOD__ ); + $condition[] = 'rev_user >' . (int)($max - $max / 100); + $index = 'user_timestamp'; + } else { + $condition['rev_user_text'] = $this->target; + $index = 'usertext_timestamp'; + } + return array( $index, $condition ); + } - // block or block log link - $id = $this->user->getId(); - if ( ( $id != 0 && $wgSysopUserBans ) || ( $id == 0 && User::isIP( $username ) ) ) { - if( $wgUser->isAllowed( 'block' ) ) - $tools[] = $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Blockip', $username ), - wfMsgHtml( 'blocklink' ) ); - else - $tools[] = $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), - htmlspecialchars( LogPage::logName( 'block' ) ), - 'type=block&page=' . $userpage->getPrefixedUrl() ); + function getNamespaceCond() { + if ( $this->namespace !== '' ) { + return array( 'page_namespace' => (int)$this->namespace ); + } else { + return array(); + } + } + + function getDateCond() { + if ( $this->year || $this->month ) { + // Assume this year if only a month is given + if ( $this->year ) { + $year_start = $this->year; + } else { + $year_start = substr( wfTimestampNow(), 0, 4 ); + $thisMonth = gmdate( 'n' ); + if( $this->month > $thisMonth ) { + // Future contributions aren't supposed to happen. :) + $year_start--; + } + } + + if ( $this->month ) { + $month_end = str_pad($this->month + 1, 2, '0', STR_PAD_LEFT); + $year_end = $year_start; + } else { + $month_end = 0; + $year_end = $year_start + 1; + } + $ts_end = str_pad($year_end . $month_end, 14, '0' ); + + $this->mOffset = $ts_end; } + } - // other logs link - $tools[] = $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), - wfMsgHtml( 'log' ), - 'user=' . $userpage->getPartialUrl() ); + function getIndexField() { + return 'rev_timestamp'; + } - return $userlink . ' (' . implode( ' | ', $tools ) . ')'; + function getStartBody() { + return "\n"; } /** - * If the user has deleted contributions and we are allowed to - * view them, generate a link to Special:DeletedContributions. - * @return string + * Generates each row in the contributions list. + * + * Contributions which are marked "top" are currently on top of the history. + * For these contributions, a [rollback] link is shown for users with roll- + * back privileges. The rollback link restores the most recent version that + * was not written by the target user. + * + * @todo This would probably look a lot nicer in a table. */ - function getDeletedContributionsLink() { - global $wgUser; + function formatRow( $row ) { + wfProfileIn( __METHOD__ ); + + global $wgLang, $wgUser, $wgContLang; + + $sk = $this->getSkin(); + $rev = new Revision( $row ); + + $page = Title::makeTitle( $row->page_namespace, $row->page_title ); + $link = $sk->makeKnownLinkObj( $page ); + $difftext = $topmarktext = ''; + if( $row->rev_id == $row->page_latest ) { + $topmarktext .= '' . $this->messages['uctop'] . ''; + if( !$row->page_is_new ) { + $difftext .= '(' . $sk->makeKnownLinkObj( $page, $this->messages['diff'], 'diff=0' ) . ')'; + } else { + $difftext .= $this->messages['newarticle']; + } + + if( !$page->getUserPermissionsErrors( 'rollback', $wgUser ) + && !$page->getUserPermissionsErrors( 'edit', $wgUser ) ) { + $topmarktext .= ' '.$sk->generateRollback( $rev ); + } - if( !$wgUser->isAllowed( 'deletedhistory' ) ) - return ''; + } + # Is there a visable previous revision? + if( $rev->userCan(Revision::DELETED_TEXT) ) { + $difftext = '(' . $sk->makeKnownLinkObj( $page, $this->messages['diff'], 'diff=prev&oldid='.$row->rev_id ) . ')'; + } else { + $difftext = '(' . $this->messages['diff'] . ')'; + } + $histlink='('.$sk->makeKnownLinkObj( $page, $this->messages['hist'], 'action=history' ) . ')'; - $dbr = wfGetDB( DB_SLAVE ); - $n = $dbr->selectField( 'archive', 'count(*)', array( 'ar_user_text' => $this->getUsername() ), __METHOD__ ); + $comment = $wgContLang->getDirMark() . $sk->revComment( $rev, false, true ); + $d = $wgLang->timeanddate( wfTimestamp( TS_MW, $row->rev_timestamp ), true ); + + if( $this->target == 'newbies' ) { + $userlink = ' . . ' . $sk->userLink( $row->rev_user, $row->rev_user_text ); + $userlink .= ' (' . $sk->userTalkLink( $row->rev_user, $row->rev_user_text ) . ') '; + } else { + $userlink = ''; + } - if ( $n == 0 ) - return ''; + if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { + $d = '' . $d . ''; + } + + if( $rev->getParentId() === 0 ) { + $nflag = '' . $this->messages['newpageletter'] . ''; + } else { + $nflag = ''; + } - $msg = wfMsg( ( $wgUser->isAllowed( 'delete' ) ? 'thisisdeleted' : 'viewdeleted' ), - $wgUser->getSkin()->makeKnownLinkObj( - SpecialPage::getTitleFor( 'DeletedContributions', $this->getUsername() ), - wfMsgExt( 'restorelink', array( 'parsemag', 'escape' ), $n ) ) ); + if( $row->rev_minor_edit ) { + $mflag = '' . $this->messages['minoreditletter'] . ' '; + } else { + $mflag = ''; + } - return "

$msg

"; + $ret = "{$d} {$histlink} {$difftext} {$nflag}{$mflag} {$link}{$userlink}{$comment} {$topmarktext}"; + if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { + $ret .= ' ' . wfMsgHtml( 'deletedrev' ); + } + $ret = "
  • $ret
  • \n"; + wfProfileOut( __METHOD__ ); + return $ret; } - + /** - * Construct and output the page subtitle. + * Get the Database object in use + * + * @return Database */ - function outputSubtitle() { - global $wgOut; - $subtitle = $this->getSubtitleForTarget(); - // $subtitle .= $this->getDeletedContributionsLink(); NOT YET... - $wgOut->setSubtitle( $subtitle ); + public function getDatabase() { + return $this->mDb; } + +} - /** - * Construct the namespace selector form. - * @return string - */ - function getNamespaceForm() { - $title = $this->getTitle(); - - $ns = $this->namespace; - if ( !isset($ns) ) - $ns = ''; +/** + * Special page "user contributions". + * Shows a list of the contributions of a user. + * + * @return none + * @param $par String: (optional) user name of the user for which to show the contributions + */ +function wfSpecialContributions( $par = null ) { + global $wgUser, $wgOut, $wgLang, $wgRequest; - $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $title->getLocalUrl() ) ); - $form .= wfMsgHtml( 'namespace' ) . ' '; - $form .= Xml::namespaceSelector( $ns, '' ) . ' '; - $form .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ); - $form .= Xml::hidden( 'offset', $this->offset ); - $form .= Xml::hidden( 'limit', $this->limit ); - $form .= Xml::hidden( 'target', $this->getUsername() ); - if ( $this->botmode ) - $form .= Xml::hidden( 'bot', 1 ); - $form .= ''; + $options = array(); + + if ( isset( $par ) && $par == 'newbies' ) { + $target = 'newbies'; + $options['contribs'] = 'newbie'; + } elseif ( isset( $par ) ) { + $target = $par; + } else { + $target = $wgRequest->getVal( 'target' ); + } - return '

    ' . $form . '

    '; + // check for radiobox + if ( $wgRequest->getVal( 'contribs' ) == 'newbie' ) { + $target = 'newbies'; + $options['contribs'] = 'newbie'; } - /** - * Build the page header. Also calls outputSubtitle(). - * @return string - */ - function getPageHeader() { - $this->outputSubtitle(); - return $this->getNamespaceForm(); + if ( !strlen( $target ) ) { + $wgOut->addHTML( contributionsForm( '' ) ); + return; } - /** - * Construct the WHERE clause of the SQL SELECT statement for - * this query. - * @return string - */ - function makeSQLCond( $dbr ) { - $cond = ' page_id = rev_page'; - $cond .= ' AND rev_user_text = ' . $dbr->addQuotes( $this->getUsername() ); + $options['limit'] = $wgRequest->getInt( 'limit', 50 ); + $options['target'] = $target; - if ( isset($this->namespace) ) - $cond .= ' AND page_namespace = ' . (int)$this->namespace; + $nt = Title::makeTitleSafe( NS_USER, $target ); + if ( !$nt ) { + $wgOut->addHTML( contributionsForm( '' ) ); + return; + } + $id = User::idFromName( $nt->getText() ); - return $cond; + if ( $target != 'newbies' ) { + $target = $nt->getText(); + $wgOut->setSubtitle( contributionsSub( $nt, $id ) ); + } else { + $wgOut->setSubtitle( wfMsgHtml( 'sp-contributions-newbies-sub') ); + } + + if ( ( $ns = $wgRequest->getVal( 'namespace', null ) ) !== null && $ns !== '' ) { + $options['namespace'] = intval( $ns ); + } else { + $options['namespace'] = ''; + } + if ( $wgUser->isAllowed( 'markbotedit' ) && $wgRequest->getBool( 'bot' ) ) { + $options['bot'] = '1'; + } + + $skip = $wgRequest->getText( 'offset' ) || $wgRequest->getText( 'dir' ) == 'prev'; + # Offset overrides year/month selection + if ( ( $month = $wgRequest->getIntOrNull( 'month' ) ) !== null && $month !== -1 ) { + $options['month'] = intval( $month ); + } else { + $options['month'] = ''; + } + if ( ( $year = $wgRequest->getIntOrNull( 'year' ) ) !== null ) { + $options['year'] = intval( $year ); + } else if( $options['month'] ) { + $thisMonth = intval( gmdate( 'n' ) ); + $thisYear = intval( gmdate( 'Y' ) ); + if( intval( $options['month'] ) > $thisMonth ) { + $thisYear--; + } + $options['year'] = $thisYear; + } else { + $options['year'] = ''; } - /** - * Construct the SQL SELECT statement for this query. - * @return string - */ - function getSQL() { - $dbr = wfGetDB( DB_SLAVE ); + wfRunHooks( 'SpecialContributionsBeforeMainOutput', $id ); - list( $page, $revision ) = $dbr->tableNamesN( 'page', 'revision' ); + if( $skip ) { + $options['year'] = ''; + $options['month'] = ''; + } - // XXX: the username and userid fields aren't used for much here, - // but some subclasses rely on them more than we do. + $wgOut->addHTML( contributionsForm( $options ) ); - return "SELECT 'Contributions' as type, - page_namespace AS namespace, - page_title AS title, - rev_timestamp AS value, - rev_user AS userid, - rev_user_text AS username, - rev_minor_edit AS is_minor, - page_latest AS cur_id, - rev_id AS rev_id, - rev_comment AS comment, - rev_deleted AS deleted - FROM $page, $revision - WHERE " . $this->makeSQLCond( $dbr ); + $pager = new ContribsPager( $target, $options['namespace'], $options['year'], $options['month'] ); + if ( !$pager->getNumRows() ) { + $wgOut->addWikiMsg( 'nocontribs' ); + return; } - /** - * Get user links for output row, for subclasses that may want - * such functionality. - * - * @param $skin Skin to use - * @param $row Result row - * @return string - */ - function getRowUserLinks( $skin, $row ) { return ''; } + # Show a message about slave lag, if applicable + if( ( $lag = $pager->getDatabase()->getLag() ) > 0 ) + $wgOut->showLagWarning( $lag ); - /** - * Format a row, providing the timestamp, links to the - * page/diff/history and a comment - * - * @param $skin Skin to use - * @param $row Result row - * @return string - */ - function formatResult( $skin, $row ) { - global $wgLang, $wgContLang, $wgUser; - - $dm = $wgContLang->getDirMark(); - - /* - * Cache UI messages in a static array so we don't - * have to regenerate them for each row. - */ - static $messages; - if( !isset( $messages ) ) { - foreach( explode( ' ', 'uctop diff newarticle rollbacklink diff hist minoreditletter' ) as $msg ) - $messages[$msg] = wfMsgExt( $msg, array( 'escape') ); + $wgOut->addHTML( + '

    ' . $pager->getNavigationBar() . '

    ' . + $pager->getBody() . + '

    ' . $pager->getNavigationBar() . '

    ' ); + + # If there were contributions, and it was a valid user or IP, show + # the appropriate "footer" message - WHOIS tools, etc. + if( $target != 'newbies' ) { + $message = IP::isIPAddress( $target ) + ? 'sp-contributions-footer-anon' + : 'sp-contributions-footer'; + + + $text = wfMsgNoTrans( $message, $target ); + if( !wfEmptyMsg( $message, $text ) && $text != '-' ) { + $wgOut->addHtml( '' ); } + } +} - $page = Title::makeTitle( $row->namespace, $row->title ); - - /* - * HACK: We need a revision object, so we make a very - * heavily stripped-down one. All we really need are - * the comment, the title and the deletion bitmask. - */ - $rev = new Revision( array( - 'comment' => $row->comment, - 'deleted' => $row->deleted, - 'user_text' => $row->username, - 'user' => $row->userid, - ) ); - $rev->setTitle( $page ); - - $ts = wfTimestamp( TS_MW, $row->value ); - $time = $wgLang->timeAndDate( $ts, true ); - $hist = $skin->makeKnownLinkObj( $page, $messages['hist'], 'action=history' ); - - if ( $rev->userCan( Revision::DELETED_TEXT ) ) - $diff = $skin->makeKnownLinkObj( $page, $messages['diff'], 'diff=prev&oldid=' . $row->rev_id ); - else - $diff = $messages['diff']; - - if( $row->is_minor ) - $mflag = '' . $messages['minoreditletter'] . ' '; - else - $mflag = ''; +/** + * Generates the subheading with links + * @param Title $nt Title object for the target + * @param integer $id User ID for the target + * @return String: appropriately-escaped HTML to be output literally + */ +function contributionsSub( $nt, $id ) { + global $wgSysopUserBans, $wgLang, $wgUser; - $link = $skin->makeKnownLinkObj( $page ); - $comment = $skin->revComment( $rev ); + $sk = $wgUser->getSkin(); - $user = $this->getRowUserLinks( $skin, $row ); // for subclasses + if ( 0 == $id ) { + $user = $nt->getText(); + } else { + $user = $sk->makeLinkObj( $nt, htmlspecialchars( $nt->getText() ) ); + } + $talk = $nt->getTalkPage(); + if( $talk ) { + # Talk page link + $tools[] = $sk->makeLinkObj( $talk, wfMsgHtml( 'talkpagelinktext' ) ); + if( ( $id != 0 && $wgSysopUserBans ) || ( $id == 0 && User::isIP( $nt->getText() ) ) ) { + # Block link + if( $wgUser->isAllowed( 'block' ) ) + $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Blockip', $nt->getDBkey() ), wfMsgHtml( 'blocklink' ) ); + # Block log link + $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), wfMsgHtml( 'sp-contributions-blocklog' ), 'type=block&page=' . $nt->getPrefixedUrl() ); + } + # Other logs link + $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), wfMsgHtml( 'log' ), 'user=' . $nt->getPartialUrl() ); - $notes = ''; + wfRunHooks( 'ContributionsToolLinks', array( $id, $nt, &$tools ) ); - if( $row->rev_id == $row->cur_id ) { - $notes .= ' ' . $messages['uctop'] . ''; + $links = implode( ' | ', $tools ); + } - if( $wgUser->isAllowed( 'rollback' ) ) - $notes .= ' ' . $skin->generateRollback( $rev ); - } - - if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { - $time = '' . $time . ''; - $notes .= ' ' . wfMsgHtml( 'deletedrev' ); - } - - return "{$time} ({$hist}) ({$diff}) {$mflag} {$dm}{$link}{$user} {$comment}{$notes}"; + // Old message 'contribsub' had one parameter, but that doesn't work for + // languages that want to put the "for" bit right after $user but before + // $links. If 'contribsub' is around, use it for reverse compatibility, + // otherwise use 'contribsub2'. + if( wfEmptyMsg( 'contribsub', wfMsg( 'contribsub' ) ) ) { + return wfMsgHtml( 'contribsub2', $user, $links ); + } else { + return wfMsgHtml( 'contribsub', "$user ($links)" ); } } /** - * Show the special page. + * Generates the namespace selector form with hidden attributes. + * @param $options Array: the options to be included. */ -function wfSpecialContributions( $par = null ) { - global $wgRequest, $wgUser, $wgOut; +function contributionsForm( $options ) { + global $wgScript, $wgTitle, $wgRequest; + + $options['title'] = $wgTitle->getPrefixedText(); + if ( !isset( $options['target'] ) ) { + $options['target'] = ''; + } else { + $options['target'] = str_replace( '_' , ' ' , $options['target'] ); + } - $username = ( isset($par) ? $par : $wgRequest->getVal( 'target' ) ); + if ( !isset( $options['namespace'] ) ) { + $options['namespace'] = ''; + } - // compatibility hack - if ( $username == 'newbies' ) { - $wgOut->redirect( SpecialPage::getTitleFor( 'NewbieContributions' )->getFullURL() ); - return; + if ( !isset( $options['contribs'] ) ) { + $options['contribs'] = 'user'; + } + + if ( !isset( $options['year'] ) ) { + $options['year'] = ''; } - $page = new ContributionsPage( $username ); + if ( !isset( $options['month'] ) ) { + $options['month'] = ''; + } - if( !$page->user ) { - $wgOut->showErrorPage( 'notargettitle', 'notargettext' ); - return; + if ( $options['contribs'] == 'newbie' ) { + $options['target'] = ''; } - // hook for Contributionseditcount extension - if ( $page->user && $page->user->isLoggedIn() ) - wfRunHooks( 'SpecialContributionsBeforeMainOutput', $page->user->getId() ); - - $page->namespace = $wgRequest->getIntOrNull( 'namespace' ); - $page->botmode = ( $wgUser->isAllowed( 'rollback' ) && $wgRequest->getBool( 'bot' ) ); + $f = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); + + foreach ( $options as $name => $value ) { + if ( in_array( $name, array( 'namespace', 'target', 'contribs', 'year', 'month' ) ) ) { + continue; + } + $f .= "\t" . Xml::hidden( $name, $value ) . "\n"; + } + + $f .= '
    ' . + Xml::element( 'legend', array(), wfMsg( 'sp-contributions-search' ) ) . + Xml::radioLabel( wfMsgExt( 'sp-contributions-newbies', array( 'parseinline' ) ), 'contribs' , 'newbie' , 'newbie', $options['contribs'] == 'newbie' ? true : false ) . '
    ' . + Xml::radioLabel( wfMsgExt( 'sp-contributions-username', array( 'parseinline' ) ), 'contribs' , 'user', 'user', $options['contribs'] == 'user' ? true : false ) . ' ' . + Xml::input( 'target', 20, $options['target']) . ' '. + '' . + Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' . + Xml::namespaceSelector( $options['namespace'], '' ) . + '' . + Xml::openElement( 'p' ) . + '' . + Xml::label( wfMsg( 'year' ), 'year' ) . ' '. + Xml::input( 'year', 4, $options['year'], array('id' => 'year', 'maxlength' => 4) ) . + '' . + ' '. + '' . + Xml::label( wfMsg( 'month' ), 'month' ) . ' '. + Xml::monthSelector( $options['month'], -1 ) . ' '. + '' . + Xml::submitButton( wfMsg( 'sp-contributions-submit' ) ) . + Xml::closeElement( 'p' ); - list( $limit, $offset ) = wfCheckLimits(); - return $page->doQuery( $offset, $limit ); + $explain = wfMsgExt( 'sp-contributions-explain', 'parseinline' ); + if( !wfEmptyMsg( 'sp-contributions-explain', $explain ) ) + $f .= "

    {$explain}

    "; + + $f .= '
    ' . + Xml::closeElement( 'form' ); + return $f; } - -?>