Merge "(bug 47070) check content model namespace on import."
[lhc/web/wiklou.git] / includes / specials / SpecialWatchlist.php
index 5a77b48..519b6c2 100644 (file)
  * http://www.gnu.org/copyleft/gpl.html
  *
  * @file
- * @ingroup SpecialPage Watchlist
+ * @ingroup SpecialPage
  */
-class SpecialWatchlist extends SpecialRecentChanges {
-       protected $customFilters;
 
+/**
+ * A special page that lists last changes made to the wiki,
+ * limited to user-defined list of titles.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialWatchlist extends ChangesListSpecialPage {
        /**
         * Constructor
         */
@@ -30,84 +35,21 @@ class SpecialWatchlist extends SpecialRecentChanges {
                parent::__construct( $page, $restriction );
        }
 
-       public function isIncludable() {
-               return false;
-       }
-
        /**
-        * Map old pre-1.23 request parameters Watchlist used to use (different from Recentchanges' ones)
-        * to the current ones.
+        * Main execution point
         *
-        * This creates derivative context and request, pokes with request's parameters, and sets them as
-        * the context for this class instance, mapping old keys to new ones completely transparently (as
-        * long as nothing tries to access the globals instead of current context).
+        * @param string $subpage
         */
-       private function mapCompatibilityRequestParameters() {
-               static $map = array(
-                       'hideMinor' => 'hideminor',
-                       'hideBots' => 'hidebots',
-                       'hideAnons' => 'hideanons',
-                       'hideLiu' => 'hideliu',
-                       'hidePatrolled' => 'hidepatrolled',
-                       'hideOwn' => 'hidemyself',
-               );
-
-               $params = $this->getRequest()->getValues();
-               foreach ( $map as $from => $to ) {
-                       if ( isset( $params[$from] ) ) {
-                               $params[$to] = $params[$from];
-                               unset( $params[$from] );
-                       }
-               }
+       function execute( $subpage ) {
+               global $wgEnotifWatchlist, $wgShowUpdatedMarker;
 
-               $context = new DerivativeContext( $this->getContext() );
-               $request = new DerivativeRequest( $context->getRequest(), $params );
-               $context->setRequest( $request );
-               $this->setContext( $context );
-       }
-
-       /**
-        * Execute
-        * @param $par Parameter passed to the page
-        */
-       function execute( $par ) {
-               global $wgRCShowWatchingUsers, $wgEnotifWatchlist, $wgShowUpdatedMarker;
-
-               $this->mapCompatibilityRequestParameters();
-
-               $user = $this->getUser();
-               $output = $this->getOutput();
-               $output->addModuleStyles( 'mediawiki.special.changeslist' );
-               $output->addModules( 'mediawiki.special.changeslist.js' );
-
-               # Anons don't get a watchlist
+               // Anons don't get a watchlist
                $this->requireLogin( 'watchlistanontext' );
 
-               // Check permissions
-               $this->checkPermissions();
-
-               // Add feed links
-               $wlToken = $user->getTokenFromOption( 'watchlisttoken' );
-               if ( $wlToken ) {
-                       $this->addFeedLinks( array(
-                               'action' => 'feedwatchlist',
-                               'allrev' => 1,
-                               'wlowner' => $user->getName(),
-                               'wltoken' => $wlToken,
-                       ) );
-               }
-
-               $this->setHeaders();
-               $this->outputHeader();
-
-               $output->addSubtitle(
-                       $this->msg( 'watchlistfor2', $user->getName() )
-                               ->rawParams( SpecialEditWatchlist::buildTools( null ) )
-               );
-
+               $output = $this->getOutput();
                $request = $this->getRequest();
 
-               $mode = SpecialEditWatchlist::getMode( $request, $par );
+               $mode = SpecialEditWatchlist::getMode( $request, $subpage );
                if ( $mode !== false ) {
                        if ( $mode === SpecialEditWatchlist::EDIT_RAW ) {
                                $title = SpecialPage::getTitleFor( 'EditWatchlist', 'raw' );
@@ -119,120 +61,168 @@ class SpecialWatchlist extends SpecialRecentChanges {
                        return;
                }
 
-               $dbr = wfGetDB( DB_SLAVE, 'watchlist' );
+               $this->checkPermissions();
 
-               $nitems = $this->countItems( $dbr );
-               if ( $nitems == 0 ) {
-                       $output->addWikiMsg( 'nowatchlist' );
+               $user = $this->getUser();
+               $opts = $this->getOptions();
+
+               if ( ( $wgEnotifWatchlist || $wgShowUpdatedMarker ) && $request->getVal( 'reset' ) &&
+                       $request->wasPosted() )
+               {
+                       $user->clearAllNotifications();
+                       $output->redirect( $this->getPageTitle()->getFullURL( $opts->getChangedValues() ) );
                        return;
                }
 
-               // @todo use FormOptions!
-               $defaults = array(
-               /* float */ 'days' => floatval( $user->getOption( 'watchlistdays' ) ),
-               /* bool  */ 'hideminor' => (int)$user->getBoolOption( 'watchlisthideminor' ),
-               /* bool  */ 'hidebots' => (int)$user->getBoolOption( 'watchlisthidebots' ),
-               /* bool  */ 'hideanons' => (int)$user->getBoolOption( 'watchlisthideanons' ),
-               /* bool  */ 'hideliu' => (int)$user->getBoolOption( 'watchlisthideliu' ),
-               /* bool  */ 'hidepatrolled' => (int)$user->getBoolOption( 'watchlisthidepatrolled' ),
-               /* bool  */ 'hidemyself' => (int)$user->getBoolOption( 'watchlisthideown' ),
-               /* bool  */ 'extended' => (int)$user->getBoolOption( 'extendwatchlist' ),
-               /* ?     */ 'namespace' => '', //means all
-               /* ?     */ 'invert' => false,
-               /* bool  */ 'associated' => false,
-               );
-               $this->customFilters = array();
-               wfRunHooks( 'SpecialWatchlistFilters', array( $this, &$this->customFilters ) );
-               foreach ( $this->customFilters as $key => $params ) {
-                       $defaults[$key] = $params['default'];
-               }
+               parent::execute( $subpage );
+       }
 
-               # Extract variables from the request, falling back to user preferences or
-               # other default values if these don't exist
-               $values = array();
-               $values['days'] = floatval( $request->getVal( 'days', $defaults['days'] ) );
-               $values['hideminor'] = (int)$request->getBool( 'hideminor', $defaults['hideminor'] );
-               $values['hidebots'] = (int)$request->getBool( 'hidebots', $defaults['hidebots'] );
-               $values['hideanons'] = (int)$request->getBool( 'hideanons', $defaults['hideanons'] );
-               $values['hideliu'] = (int)$request->getBool( 'hideliu', $defaults['hideliu'] );
-               $values['hidemyself'] = (int)$request->getBool( 'hidemyself', $defaults['hidemyself'] );
-               $values['hidepatrolled'] = (int)$request->getBool( 'hidepatrolled', $defaults['hidepatrolled'] );
-               $values['extended'] = (int)$request->getBool( 'extended', $defaults['extended'] );
-               foreach ( $this->customFilters as $key => $params ) {
-                       $values[$key] = (int)$request->getBool( $key, $defaults[$key] );
+       /**
+        * Get a FormOptions object containing the default options
+        *
+        * @return FormOptions
+        */
+       public function getDefaultOptions() {
+               $opts = parent::getDefaultOptions();
+               $user = $this->getUser();
+
+               $opts->add( 'days', $user->getOption( 'watchlistdays' ), FormOptions::FLOAT );
+
+               $opts->add( 'hideminor', $user->getBoolOption( 'watchlisthideminor' ) );
+               $opts->add( 'hidebots', $user->getBoolOption( 'watchlisthidebots' ) );
+               $opts->add( 'hideanons', $user->getBoolOption( 'watchlisthideanons' ) );
+               $opts->add( 'hideliu', $user->getBoolOption( 'watchlisthideliu' ) );
+               $opts->add( 'hidepatrolled', $user->getBoolOption( 'watchlisthidepatrolled' ) );
+               $opts->add( 'hidemyself', $user->getBoolOption( 'watchlisthideown' ) );
+
+               $opts->add( 'extended', $user->getBoolOption( 'extendwatchlist' ) );
+
+               return $opts;
+       }
+
+       /**
+        * Get custom show/hide filters
+        *
+        * @return array Map of filter URL param names to properties (msg/default)
+        */
+       protected function getCustomFilters() {
+               if ( $this->customFilters === null ) {
+                       $this->customFilters = array();
+                       wfRunHooks( 'SpecialWatchlistFilters', array( $this, &$this->customFilters ) );
                }
 
-               # Get namespace value, if supplied, and prepare a WHERE fragment
-               $nameSpace = $request->getIntOrNull( 'namespace' );
-               $invert = $request->getBool( 'invert' );
-               $associated = $request->getBool( 'associated' );
-               if ( !is_null( $nameSpace ) ) {
-                       $eq_op = $invert ? '!=' : '=';
-                       $bool_op = $invert ? 'AND' : 'OR';
-                       $nameSpace = intval( $nameSpace ); // paranioa
-                       if ( !$associated ) {
-                               $nameSpaceClause = "rc_namespace $eq_op $nameSpace";
-                       } else {
-                               $associatedNS = MWNamespace::getAssociated( $nameSpace );
-                               $nameSpaceClause =
-                                       "rc_namespace $eq_op $nameSpace " .
-                                       $bool_op .
-                                       " rc_namespace $eq_op $associatedNS";
+               return $this->customFilters;
+       }
+
+       /**
+        * Fetch values for a FormOptions object from the WebRequest associated with this instance.
+        *
+        * Maps old pre-1.23 request parameters Watchlist used to use (different from Recentchanges' ones)
+        * to the current ones.
+        *
+        * @param FormOptions $parameters
+        * @return FormOptions
+        */
+       protected function fetchOptionsFromRequest( $opts ) {
+               static $compatibilityMap = array(
+                       'hideMinor' => 'hideminor',
+                       'hideBots' => 'hidebots',
+                       'hideAnons' => 'hideanons',
+                       'hideLiu' => 'hideliu',
+                       'hidePatrolled' => 'hidepatrolled',
+                       'hideOwn' => 'hidemyself',
+               );
+
+               $params = $this->getRequest()->getValues();
+               foreach ( $compatibilityMap as $from => $to ) {
+                       if ( isset( $params[$from] ) ) {
+                               $params[$to] = $params[$from];
+                               unset( $params[$from] );
                        }
-               } else {
-                       $nameSpace = '';
-                       $nameSpaceClause = '';
-               }
-               $values['namespace'] = $nameSpace;
-               $values['invert'] = $invert;
-               $values['associated'] = $associated;
-
-               // Dump everything here
-               $nondefaults = array();
-               foreach ( $defaults as $name => $defValue ) {
-                       wfAppendToArrayIfNotDefault( $name, $values[$name], $defaults, $nondefaults );
                }
 
-               if ( ( $wgEnotifWatchlist || $wgShowUpdatedMarker ) && $request->getVal( 'reset' )
-                       && $request->wasPosted()
-               ) {
-                       $user->clearAllNotifications();
-                       $output->redirect( $this->getPageTitle()->getFullURL( $nondefaults ) );
-                       return;
-               }
+               // Not the prettiest way to achieve this… FormOptions internally depends on data sanitization
+               // methods defined on WebRequest and removing this dependency would cause some code duplication.
+               $request = new DerivativeRequest( $this->getRequest(), $params );
+               $opts->fetchValuesFromRequest( $request );
+               return $opts;
+       }
 
-               # Possible where conditions
+       /**
+        * Return an array of conditions depending of options set in $opts
+        *
+        * @param FormOptions $opts
+        * @return array
+        */
+       public function buildMainQueryConds( FormOptions $opts ) {
+               $dbr = wfGetDB( DB_SLAVE, 'watchlist' );
                $conds = array();
+               $user = $this->getUser();
 
-               if ( $values['days'] > 0 ) {
-                       $conds[] = 'rc_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( time() - intval( $values['days'] * 86400 ) ) );
+               // Calculate cutoff
+               if ( $opts['days'] > 0 ) {
+                       $conds[] = 'rc_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( time() - intval( $opts['days'] * 86400 ) ) );
                }
 
                # Toggles
-               if ( $values['hidemyself'] ) {
+               if ( $opts['hidemyself'] ) {
                        $conds[] = 'rc_user != ' . $user->getId();
                }
-               if ( $values['hidebots'] ) {
+               if ( $opts['hidebots'] ) {
                        $conds[] = 'rc_bot = 0';
                }
-               if ( $values['hideminor'] ) {
+               if ( $opts['hideminor'] ) {
                        $conds[] = 'rc_minor = 0';
                }
-               if ( $values['hideliu'] ) {
+               if ( $opts['hideliu'] ) {
                        $conds[] = 'rc_user = 0';
                }
-               if ( $values['hideanons'] ) {
+               if ( $opts['hideanons'] ) {
                        $conds[] = 'rc_user != 0';
                }
-               if ( $user->useRCPatrol() && $values['hidepatrolled'] ) {
+               if ( $user->useRCPatrol() && $opts['hidepatrolled'] ) {
                        $conds[] = 'rc_patrolled != 1';
                }
-               if ( $nameSpaceClause ) {
-                       $conds[] = $nameSpaceClause;
+
+               # Namespace filtering
+               if ( $opts['namespace'] !== '' ) {
+                       $selectedNS = $dbr->addQuotes( $opts['namespace'] );
+                       $operator = $opts['invert'] ? '!=' : '=';
+                       $boolean = $opts['invert'] ? 'AND' : 'OR';
+
+                       # namespace association (bug 2429)
+                       if ( !$opts['associated'] ) {
+                               $condition = "rc_namespace $operator $selectedNS";
+                       } else {
+                               # Also add the associated namespace
+                               $associatedNS = $dbr->addQuotes(
+                                       MWNamespace::getAssociated( $opts['namespace'] )
+                               );
+                               $condition = "(rc_namespace $operator $selectedNS "
+                                       . $boolean
+                                       . " rc_namespace $operator $associatedNS)";
+                       }
+
+                       $conds[] = $condition;
                }
 
+               return $conds;
+       }
+
+       /**
+        * Process the query
+        *
+        * @param array $conds
+        * @param FormOptions $opts
+        * @return bool|ResultWrapper Result or false (for Recentchangeslinked only)
+        */
+       public function doMainQuery( $conds, $opts ) {
+               global $wgShowUpdatedMarker;
+
+               $dbr = wfGetDB( DB_SLAVE, 'watchlist' );
+               $user = $this->getUser();
                # Toggle watchlist content (all recent edits or just the latest)
-               if ( $values['extended'] ) {
+               if ( $opts['extended'] ) {
                        $limitWatchlist = $user->getIntOption( 'wllimit' );
                        $usePage = false;
                } else {
@@ -252,51 +242,6 @@ class SpecialWatchlist extends SpecialRecentChanges {
                        $usePage = true;
                }
 
-               # Show a message about slave lag, if applicable
-               $lag = wfGetLB()->safeGetLag( $dbr );
-               if ( $lag > 0 ) {
-                       $output->showLagWarning( $lag );
-               }
-
-               # Create output
-               $form = '';
-
-               # Show watchlist header
-               $form .= "<p>";
-               $form .= $this->msg( 'watchlist-details' )->numParams( $nitems )->parse() . "\n";
-               if ( $wgEnotifWatchlist && $user->getOption( 'enotifwatchlistpages' ) ) {
-                       $form .= $this->msg( 'wlheader-enotif' )->parse() . "\n";
-               }
-               if ( $wgShowUpdatedMarker ) {
-                       $form .= $this->msg( 'wlheader-showupdated' )->parse() . "\n";
-               }
-               $form .= "</p>";
-
-               if ( $wgShowUpdatedMarker ) {
-                       $form .= Xml::openElement( 'form', array( 'method' => 'post',
-                               'action' => $this->getPageTitle()->getLocalURL(),
-                               'id' => 'mw-watchlist-resetbutton' ) ) . "\n" .
-                       Xml::submitButton( $this->msg( 'enotif_reset' )->text(), array( 'name' => 'dummy' ) ) . "\n" .
-                       Html::hidden( 'reset', 'all' ) . "\n";
-                       foreach ( $nondefaults as $key => $value ) {
-                               $form .= Html::hidden( $key, $value ) . "\n";
-                       }
-                       $form .= Xml::closeElement( 'form' ) . "\n";
-               }
-
-               $form .= Xml::openElement( 'form', array(
-                       'method' => 'post',
-                       'action' => $this->getPageTitle()->getLocalURL(),
-                       'id' => 'mw-watchlist-form'
-               ) );
-               $form .= Xml::fieldset(
-                       $this->msg( 'watchlist-options' )->text(),
-                       false,
-                       array( 'id' => 'mw-watchlist-options' )
-               );
-
-               $form .= SpecialRecentChanges::makeLegend( $this->getContext() );
-
                $tables = array( 'recentchanges', 'watchlist' );
                $fields = RecentChange::selectFields();
                $join_conds = array(
@@ -326,23 +271,137 @@ class SpecialWatchlist extends SpecialRecentChanges {
                        }
                }
 
+               // Log entries with DELETED_ACTION must not show up unless the user has
+               // the necessary rights.
+               if ( !$user->isAllowed( 'deletedhistory' ) ) {
+                       $bitmask = LogPage::DELETED_ACTION;
+               } elseif ( !$user->isAllowed( 'suppressrevision' ) ) {
+                       $bitmask = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
+               } else {
+                       $bitmask = 0;
+               }
+               if ( $bitmask ) {
+                       $conds[] = $dbr->makeList( array(
+                               'rc_type != ' . RC_LOG,
+                               $dbr->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask",
+                       ), LIST_OR );
+               }
+
+
                ChangeTags::modifyDisplayQuery( $tables, $fields, $conds, $join_conds, $options, '' );
-               wfRunHooks( 'SpecialWatchlistQuery', array( &$conds, &$tables, &$join_conds, &$fields, $values ) );
+               wfRunHooks( 'SpecialWatchlistQuery', array( &$conds, &$tables, &$join_conds, &$fields, $opts ) );
+
+               return $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $join_conds );
+       }
+
+       /**
+        * Send output to the OutputPage object, only called if not used feeds
+        *
+        * @param ResultWrapper $rows Database rows
+        * @param FormOptions $opts
+        */
+       public function webOutput( $rows, $opts ) {
+               global $wgShowUpdatedMarker, $wgRCShowWatchingUsers;
+
+               $dbr = wfGetDB( DB_SLAVE, 'watchlist' );
+               $user = $this->getUser();
+
+               $dbr->dataSeek( $rows, 0 );
+
+               $list = ChangesList::newFromContext( $this->getContext() );
+               $list->setWatchlistDivs();
+
+               $s = $list->beginRecentChangesList();
+               $counter = 1;
+               foreach ( $rows as $obj ) {
+                       # Make RC entry
+                       $rc = RecentChange::newFromRow( $obj );
+                       $rc->counter = $counter++;
+
+                       if ( $wgShowUpdatedMarker ) {
+                               $updated = $obj->wl_notificationtimestamp;
+                       } else {
+                               $updated = false;
+                       }
+
+                       if ( $wgRCShowWatchingUsers && $user->getOption( 'shownumberswatching' ) ) {
+                               $rc->numberofWatchingusers = $dbr->selectField( 'watchlist',
+                                       'COUNT(*)',
+                                       array(
+                                               'wl_namespace' => $obj->rc_namespace,
+                                               'wl_title' => $obj->rc_title,
+                                       ),
+                                       __METHOD__ );
+                       } else {
+                               $rc->numberofWatchingusers = 0;
+                       }
+
+                       $changeLine = $list->recentChangesLine( $rc, $updated, $counter );
+                       if ( $changeLine !== false ) {
+                               $s .= $changeLine;
+                       }
+               }
+               $s .= $list->endRecentChangesList();
+
+               // Print things out
+
+               $output = $this->getOutput();
+
+               $output->addSubtitle(
+                       $this->msg( 'watchlistfor2', $user->getName() )
+                               ->rawParams( SpecialEditWatchlist::buildTools( null ) )
+               );
+
+               // Output options box
+               $this->doHeader( $opts );
+
+               // Add feed links
+               $wlToken = $user->getTokenFromOption( 'watchlisttoken' );
+               if ( $wlToken ) {
+                       $this->addFeedLinks( array(
+                               'action' => 'feedwatchlist',
+                               'allrev' => 1,
+                               'wlowner' => $user->getName(),
+                               'wltoken' => $wlToken,
+                       ) );
+               }
+
+               # Show a message about slave lag, if applicable
+               $lag = wfGetLB()->safeGetLag( $dbr );
+               if ( $lag > 0 ) {
+                       $output->showLagWarning( $lag );
+               }
+
+               if ( $rows->numRows() == 0 ) {
+                       $output->wrapWikiMsg(
+                               "<div class='mw-changeslist-empty'>\n$1\n</div>", 'recentchanges-noresult'
+                       );
+               } else {
+                       $output->addHTML( $s );
+               }
+       }
 
-               $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $join_conds );
-               $numRows = $res->numRows();
+       /**
+        * Return the text to be displayed above the changes
+        *
+        * @param FormOptions $opts
+        * @return string XHTML
+        */
+       public function doHeader( $opts ) {
+               $user = $this->getUser();
 
-               /* Start bottom header */
+               $this->setTopText( $opts );
 
                $lang = $this->getLanguage();
                $wlInfo = '';
-               if ( $values['days'] > 0 ) {
+               if ( $opts['days'] > 0 ) {
                        $timestamp = wfTimestampNow();
-                       $wlInfo = $this->msg( 'wlnote' )->numParams( $numRows, round( $values['days'] * 24 ) )->params(
+                       $wlInfo = $this->msg( 'wlnote2' )->numParams( round( $opts['days'] * 24 ) )->params(
                                $lang->userDate( $timestamp, $user ), $lang->userTime( $timestamp, $user ) )->parse() . "<br />\n";
                }
 
-               $cutofflinks = $this->cutoffLinks( $values['days'], $nondefaults ) . "<br />\n";
+               $nondefaults = $opts->getChangedValues();
+               $cutofflinks = $this->cutoffLinks( $opts['days'], $nondefaults ) . "<br />\n";
 
                # Spit out some control panel links
                $filters = array(
@@ -353,7 +412,7 @@ class SpecialWatchlist extends SpecialRecentChanges {
                        'hidemyself' => 'rcshowhidemine',
                        'hidepatrolled' => 'rcshowhidepatr'
                );
-               foreach ( $this->customFilters as $key => $params ) {
+               foreach ( $this->getCustomFilters() as $key => $params ) {
                        $filters[$key] = $params['msg'];
                }
                // Disable some if needed
@@ -363,7 +422,7 @@ class SpecialWatchlist extends SpecialRecentChanges {
 
                $links = array();
                foreach ( $filters as $name => $msg ) {
-                       $links[] = $this->showHideLink( $nondefaults, $msg, $name, $values[$name] );
+                       $links[] = $this->showHideLink( $nondefaults, $msg, $name, $opts[$name] );
                }
 
                $hiddenFields = $nondefaults;
@@ -371,6 +430,9 @@ class SpecialWatchlist extends SpecialRecentChanges {
                unset( $hiddenFields['invert'] );
                unset( $hiddenFields['associated'] );
 
+               # Create output
+               $form = '';
+
                # Namespace filter and put the whole form together.
                $form .= $wlInfo;
                $form .= $cutofflinks;
@@ -378,7 +440,7 @@ class SpecialWatchlist extends SpecialRecentChanges {
                $form .= "<hr />\n<p>";
                $form .= Html::namespaceSelector(
                        array(
-                               'selected' => $nameSpace,
+                               'selected' => $opts['namespace'],
                                'all' => '',
                                'label' => $this->msg( 'namespace' )->text()
                        ), array(
@@ -391,14 +453,14 @@ class SpecialWatchlist extends SpecialRecentChanges {
                        $this->msg( 'invert' )->text(),
                        'invert',
                        'nsinvert',
-                       $invert,
+                       $opts['invert'],
                        array( 'title' => $this->msg( 'tooltip-invert' )->text() )
                ) . '&#160;';
                $form .= Xml::checkLabel(
                        $this->msg( 'namespace_association' )->text(),
                        'associated',
-                       'associated',
-                       $associated,
+                       'nsassociated',
+                       $opts['associated'],
                        array( 'title' => $this->msg( 'tooltip-namespace_association' )->text() )
                ) . '&#160;';
                $form .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . "</p>\n";
@@ -407,68 +469,62 @@ class SpecialWatchlist extends SpecialRecentChanges {
                }
                $form .= Xml::closeElement( 'fieldset' ) . "\n";
                $form .= Xml::closeElement( 'form' ) . "\n";
-               $output->addHTML( $form );
-
-               # If there's nothing to show, stop here
-               if ( $numRows == 0 ) {
-                       $output->wrapWikiMsg(
-                               "<div class='mw-changeslist-empty'>\n$1\n</div>", 'recentchanges-noresult'
-                       );
-                       return;
-               }
-
-               /* End bottom header */
+               $this->getOutput()->addHTML( $form );
 
-               /* Do link batch query */
-               $linkBatch = new LinkBatch;
-               foreach ( $res as $row ) {
-                       $userNameUnderscored = str_replace( ' ', '_', $row->rc_user_text );
-                       if ( $row->rc_user != 0 ) {
-                               $linkBatch->add( NS_USER, $userNameUnderscored );
-                       }
-                       $linkBatch->add( NS_USER_TALK, $userNameUnderscored );
+               $this->setBottomText( $opts );
+       }
 
-                       $linkBatch->add( $row->rc_namespace, $row->rc_title );
-               }
-               $linkBatch->execute();
-               $dbr->dataSeek( $res, 0 );
+       function setTopText( FormOptions $opts ) {
+               global $wgEnotifWatchlist, $wgShowUpdatedMarker;
 
-               $list = ChangesList::newFromContext( $this->getContext() );
-               $list->setWatchlistDivs();
+               $nondefaults = $opts->getChangedValues();
+               $form = "";
+               $user = $this->getUser();
 
-               $s = $list->beginRecentChangesList();
-               $counter = 1;
-               foreach ( $res as $obj ) {
-                       # Make RC entry
-                       $rc = RecentChange::newFromRow( $obj );
-                       $rc->counter = $counter++;
+               $dbr = wfGetDB( DB_SLAVE, 'watchlist' );
+               $numItems = $this->countItems( $dbr );
 
-                       if ( $wgShowUpdatedMarker ) {
-                               $updated = $obj->wl_notificationtimestamp;
-                       } else {
-                               $updated = false;
+               // Show watchlist header
+               $form .= "<p>";
+               if ( $numItems == 0 ) {
+                       $form .= $this->msg( 'nowatchlist' )->parse() . "\n";
+               } else {
+                       $form .= $this->msg( 'watchlist-details' )->numParams( $numItems )->parse() . "\n";
+                       if ( $wgEnotifWatchlist && $user->getOption( 'enotifwatchlistpages' ) ) {
+                               $form .= $this->msg( 'wlheader-enotif' )->parse() . "\n";
                        }
-
-                       if ( $wgRCShowWatchingUsers && $user->getOption( 'shownumberswatching' ) ) {
-                               $rc->numberofWatchingusers = $dbr->selectField( 'watchlist',
-                                       'COUNT(*)',
-                                       array(
-                                               'wl_namespace' => $obj->rc_namespace,
-                                               'wl_title' => $obj->rc_title,
-                                       ),
-                                       __METHOD__ );
-                       } else {
-                               $rc->numberofWatchingusers = 0;
+                       if ( $wgShowUpdatedMarker ) {
+                               $form .= $this->msg( 'wlheader-showupdated' )->parse() . "\n";
                        }
+               }
+               $form .= "</p>";
 
-                       $changeLine = $list->recentChangesLine( $rc, $updated, $counter );
-                       if ( $changeLine !== false ) {
-                               $s .= $changeLine;
+               if ( $numItems > 0 && $wgShowUpdatedMarker ) {
+                       $form .= Xml::openElement( 'form', array( 'method' => 'post',
+                               'action' => $this->getPageTitle()->getLocalURL(),
+                               'id' => 'mw-watchlist-resetbutton' ) ) . "\n" .
+                       Xml::submitButton( $this->msg( 'enotif_reset' )->text(), array( 'name' => 'dummy' ) ) . "\n" .
+                       Html::hidden( 'reset', 'all' ) . "\n";
+                       foreach ( $nondefaults as $key => $value ) {
+                               $form .= Html::hidden( $key, $value ) . "\n";
                        }
+                       $form .= Xml::closeElement( 'form' ) . "\n";
                }
-               $s .= $list->endRecentChangesList();
 
-               $output->addHTML( $s );
+               $form .= Xml::openElement( 'form', array(
+                       'method' => 'post',
+                       'action' => $this->getPageTitle()->getLocalURL(),
+                       'id' => 'mw-watchlist-form'
+               ) );
+               $form .= Xml::fieldset(
+                       $this->msg( 'watchlist-options' )->text(),
+                       false,
+                       array( 'id' => 'mw-watchlist-options' )
+               );
+
+               $form .= SpecialRecentChanges::makeLegend( $this->getContext() );
+
+               $this->getOutput()->addHTML( $form );
        }
 
        protected function showHideLink( $options, $message, $name, $value ) {
@@ -533,9 +589,9 @@ class SpecialWatchlist extends SpecialRecentChanges {
         */
        protected function countItems( $dbr ) {
                # Fetch the raw count
-               $res = $dbr->select( 'watchlist', array( 'count' => 'COUNT(*)' ),
+               $rows = $dbr->select( 'watchlist', array( 'count' => 'COUNT(*)' ),
                        array( 'wl_user' => $this->getUser()->getId() ), __METHOD__ );
-               $row = $dbr->fetchObject( $res );
+               $row = $dbr->fetchObject( $rows );
                $count = $row->count;
 
                return floor( $count / 2 );