Rename 'SpecialContribSubEnd' hook to more appropriate 'ContributionsToolLinks',...
[lhc/web/wiklou.git] / includes / SpecialContributions.php
index 0a142d7..d245144 100644 (file)
 <?php
-
 /**
- * Special page allowing users to view their own contributions
- * and those of others.
- *
- * @package MediaWiki
- * @subpackage Special pages
+ * Special:Contributions, show user contributions in a paged list
+ * @addtogroup SpecialPage
  */
-class ContributionsPage extends QueryPage {
-       var $user = null;
-       var $namespace = null;
-       var $botmode = false;
 
-       /**
-        * Constructor.
-        * @param $username username to list contribs for (or "newbies" for extra magic)
-        */
-       function __construct( $username='' ) {
-               $this->user = User::newFromName( $username, false );
-       }
+class ContribsPager extends IndexPager {
+       public $mDefaultDirection = true;
+       var $messages, $target;
+       var $namespace = '', $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 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;
+               return $query;
        }
 
-       /**
-        * @return array Extra URL params for self-links.
-        */
-       function linkParameters() {
-               $params['target'] = $this->getUsername();
-
-               if ( isset($this->namespace) )
-                       $params['namespace'] = $this->namespace;
+       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_deleted'
+                       ),
+                       'conds' => $conds,
+                       'options' => array( 'FORCE INDEX' => $index )
+               );
+       }
 
-               if ( $this->botmode )
-                       $params['bot'] = 1;
+       function getUserCond() {
+               $condition = array();
 
-               return $params;
+               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 );
        }
 
-       /**
-        * 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;
+       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;
+               }
+       }
 
-               $skin = $wgUser->getSkin();
+       function getIndexField() {
+               return 'rev_timestamp';
+       }
 
-               $username = $this->getUsername();
-               $userpage = $this->user->getUserPage();
-               $userlink = $skin->makeLinkObj( $userpage, $username );
+       function getStartBody() {
+               return "<ul>\n";
+       }
 
-               // talk page link
-               $tools[] = $skin->makeLinkObj( $userpage->getTalkPage(), $wgLang->getNsText( NS_TALK ) );
+       function getEndBody() {
+               return "</ul>\n";
+       }
 
-               // 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 getNavigationBar() {
+               if ( isset( $this->mNavigationBar ) ) {
+                       return $this->mNavigationBar;
                }
-
-               // other logs link
-               $tools[] = $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ),
-                                                 wfMsgHtml( 'log' ),
-                                                 'user=' . $userpage->getPartialUrl() );
-
-               return $userlink . ' (' . implode( ' | ', $tools ) . ')';
+               $linkTexts = array(
+                       'prev' => wfMsgHtml( "sp-contributions-newer", $this->mLimit ),
+                       'next' => wfMsgHtml( 'sp-contributions-older', $this->mLimit ),
+                       'first' => wfMsgHtml('sp-contributions-newest'),
+                       'last' => wfMsgHtml( 'sp-contributions-oldest' )
+               );
+
+               $pagingLinks = $this->getPagingLinks( $linkTexts );
+               $limitLinks = $this->getLimitLinks();
+               $limits = implode( ' | ', $limitLinks );
+               
+               $this->mNavigationBar = "({$pagingLinks['first']} | {$pagingLinks['last']}) " . 
+                       wfMsgHtml("viewprevnext", $pagingLinks['prev'], $pagingLinks['next'], $limits);
+               return $this->mNavigationBar;
        }
 
        /**
-        * Generate "For User (...)" message in subtitle.  Calls
-        * getTargetUserLinks() for most of the work.
-        * @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 sysop
+        * 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 getSubtitleForTarget() {
-               return wfMsgHtml( 'contribsub', $this->getTargetUserLinks() );
-       }
+       function formatRow( $row ) {
+               wfProfileIn( __METHOD__ );
 
-       /**
-        * If the user has deleted contributions and we are allowed to
-        * view them, generate a link to Special:DeletedContributions.
-        * @return string 
-        */
-       function getDeletedContributionsLink() {
-               global $wgUser;
+               global $wgLang, $wgUser;
 
-               if( !$wgUser->isAllowed( 'deletedhistory' ) )
-                       return '';
+               $sk = $this->getSkin();
+               $rev = new Revision( $row );
 
-               $dbr = wfGetDB( DB_SLAVE );
-               $n = $dbr->selectField( 'archive', 'count(*)', array( 'ar_user_text' => $this->getUsername() ), __METHOD__ );
+               $page = Title::makeTitle( $row->page_namespace, $row->page_title );
+               $link = $sk->makeKnownLinkObj( $page );
+               $difftext = $topmarktext = '';
+               if( $row->rev_id == $row->page_latest ) {
+                       $topmarktext .= '<strong>' . $this->messages['uctop'] . '</strong>';
+                       if( !$row->page_is_new ) {
+                               $difftext .= '(' . $sk->makeKnownLinkObj( $page, $this->messages['diff'], 'diff=0' ) . ')';
+                       } else {
+                               $difftext .= $this->messages['newarticle'];
+                       }
 
-               if ( $n == 0 )
-                       return '';
+                       if( $wgUser->isAllowed( 'rollback' ) ) {
+                               $topmarktext .= ' '.$sk->generateRollback( $rev );
+                       }
 
-               $msg = wfMsg( ( $wgUser->isAllowed( 'delete' ) ? 'thisisdeleted' : 'viewdeleted' ),
-                             $wgUser->getSkin()->makeKnownLinkObj(
-                                     SpecialPage::getTitleFor( 'DeletedContributions', $this->getUsername() ),
-                                     wfMsgExt( 'restorelink', array( 'parsemag', 'escape' ), $n ) ) );
+               }
+               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' ) . ')';
 
-               return "<p>$msg</p>";
-       }
+               $comment = $sk->revComment( $rev );
+               $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 = '';
+               }
 
-       /**
-        * Construct and output the page subtitle.
-        */
-       function outputSubtitle() {
-               global $wgOut;
-               $subtitle = $this->getSubtitleForTarget();
-               // $subtitle .= $this->getDeletedContributionsLink();  NOT YET...
-               $wgOut->setSubtitle( $subtitle );
-       }
+               if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
+                       $d = '<span class="history-deleted">' . $d . '</span>';
+               }
+
+               if( $row->rev_minor_edit ) {
+                       $mflag = '<span class="minor">' . $this->messages['minoreditletter'] . '</span> ';
+               } else {
+                       $mflag = '';
+               }
 
+               $ret = "{$d} {$histlink} {$difftext} {$mflag} {$link}{$userlink}{$comment} {$topmarktext}";
+               if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
+                       $ret .= ' ' . wfMsgHtml( 'deletedrev' );
+               }
+               $ret = "<li>$ret</li>\n";
+               wfProfileOut( __METHOD__ );
+               return $ret;
+       }
+       
        /**
-        * Construct the namespace selector form.
-        * @return string 
+        * Get the Database object in use
+        *
+        * @return Database
         */
-       function getNamespaceForm() {
-               $title = $this->getTitle();
-
-               $ns = $this->namespace;
-               if ( !isset($ns) )
-                       $ns = '';
+       public function getDatabase() {
+               return $this->mDb;
+       }
+       
+}
 
-               $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 .= '</form>';
+/**
+ * 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;
 
-               return '<p>' . $form . '</p>';
+       $options = array();
+       
+       if ( isset( $par ) && $par == 'newbies' ) {
+               $target = 'newbies';
+               $options['contribs'] = 'newbie';
+       } elseif ( isset( $par ) ) {
+               $target = $par;
+       } else {
+               $target = $wgRequest->getVal( 'target' );
        }
 
-       /**
-        * Build the page header.  Also calls outputSubtitle().
-        * @return string 
-        */
-       function getPageHeader() {
-               $this->outputSubtitle();
-               return $this->getNamespaceForm();
+       // check for radiobox
+       if ( $wgRequest->getVal( 'contribs' ) == 'newbie' ) {
+               $target = 'newbies';
+               $options['contribs'] = 'newbie';
        }
 
-       /**
-        * 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() );
+       if ( !strlen( $target ) ) {
+               $wgOut->addHTML( contributionsForm( '' ) );
+               return;
+       }
 
-               if ( isset($this->namespace) )
-                       $cond .= ' AND page_namespace = ' . (int)$this->namespace;
+       $options['limit'] = $wgRequest->getInt( 'limit', 50 );
+       $options['target'] = $target;
 
-               return $cond;
+       $nt = Title::makeTitleSafe( NS_USER, $target );
+       if ( !$nt ) {
+               $wgOut->addHTML( contributionsForm( '' ) );
+               return;
        }
+       $id = User::idFromName( $nt->getText() );
 
-       /**
-        * Construct the SQL SELECT statement for this query.
-        * @return string
-        */
-       function getSQL() {
-               $dbr = wfGetDB( DB_SLAVE );
+       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( 'rollback' ) && $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'] = '';
+       }
 
-               list( $page, $revision ) = $dbr->tableNamesN( 'page', 'revision' );
+       wfRunHooks( 'SpecialContributionsBeforeMainOutput', $id );
 
-               // 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 ) );
+       # Show original selected options, don't apply them so as to allow paging
+       $_GET['year'] = ''; // hack for Pager
+       $_GET['month'] = ''; // hack for Pager
+       if( $skip ) {
+               $options['year'] = '';
+               $options['month'] = '';
+       }
 
-               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->addWikiText( wfMsg( '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( 
+               '<p>' . $pager->getNavigationBar() . '</p>' .
+               $pager->getBody() .
+               '<p>' . $pager->getNavigationBar() . '</p>' );
+       
+       # 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 = wfMsg( $message, $target );
+               if( !wfEmptyMsg( $message, $text ) && $text != '-' ) {
+                       $wgOut->addHtml( '<div class="mw-contributions-footer">' );
+                       $wgOut->addWikiText( $text );
+                       $wgOut->addHtml( '</div>' );
                }
+       }
+}
 
-               $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 = '<span class="minor">' . $messages['minoreditletter'] . '</span> ';
-               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 .= ' <strong>' . $messages['uctop'] . '</strong>';
+               $links = implode( ' | ', $tools );
+       }
 
-                       if( $wgUser->isAllowed( 'rollback' ) )
-                               $notes .= ' ' . $skin->generateRollback( $rev );
-               }
-               
-               if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
-                       $time = '<span class="history-deleted">' . $time . '</span>';
-                       $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 .= '<fieldset>' .
+               Xml::element( 'legend', array(), wfMsg( 'sp-contributions-search' ) ) .
+               Xml::radioLabel( wfMsgExt( 'sp-contributions-newbies', array( 'parseinline' ) ), 'contribs' , 'newbie' , 'newbie', $options['contribs'] == 'newbie' ? true : false ) . '<br />' .
+               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 .= "<p>{$explain}</p>";
+               
+       $f .= '</fieldset>' .
+               Xml::closeElement( 'form' );
+       return $f;
 }
-
-?>