Move WatchlistEditor.php to /specials since inside it is essentially a complete speci...
authorHappy-melon <happy-melon@users.mediawiki.org>
Thu, 24 Mar 2011 23:28:39 +0000 (23:28 +0000)
committerHappy-melon <happy-melon@users.mediawiki.org>
Thu, 24 Mar 2011 23:28:39 +0000 (23:28 +0000)
includes/AutoLoader.php
includes/SpecialPage.php
includes/WatchlistEditor.php [deleted file]
includes/specials/SpecialEditWatchlist.php [new file with mode: 0644]
includes/specials/SpecialWatchlist.php
languages/messages/MessagesEn.php

index dc7e400..f0624a3 100644 (file)
@@ -250,7 +250,7 @@ $wgAutoloadLocalClasses = array(
        'ViewCountUpdate' => 'includes/ViewCountUpdate.php',
        'WantedQueryPage' => 'includes/QueryPage.php',
        'WatchedItem' => 'includes/WatchedItem.php',
-       'WatchlistEditor' => 'includes/WatchlistEditor.php',
+       'WatchlistEditor' => 'includes/specials/SpecialEditWatchlist.php',
        'WebRequest' => 'includes/WebRequest.php',
        'WebRequestUpload' => 'includes/WebRequest.php',
        'WebResponse' => 'includes/WebResponse.php',
@@ -655,6 +655,7 @@ $wgAutoloadLocalClasses = array(
        'SpecialCategories' => 'includes/specials/SpecialCategories.php',
        'SpecialComparePages' => 'includes/specials/SpecialComparePages.php',
        'SpecialDisableAccount' => 'includes/specials/SpecialDisableAccount.php',
+       'SpecialEditWatchlist' => 'includes/specials/SpecialEditWatchlist.php',
        'SpecialExport' => 'includes/specials/SpecialExport.php',
        'SpecialFilepath' => 'includes/specials/SpecialFilepath.php',
        'SpecialImport' => 'includes/specials/SpecialImport.php',
index db95c3b..b3f0200 100644 (file)
@@ -150,6 +150,7 @@ class SpecialPage {
                'Activeusers'               => 'SpecialActiveUsers',
                'Userrights'                => 'UserrightsPage',
                'DisableAccount'            => 'SpecialDisableAccount',
+               'EditWatchlist'             => 'SpecialEditWatchlist',
 
                # Recent changes and logs
                'Newimages'                 => array( 'IncludableSpecialPage', 'Newimages' ),
diff --git a/includes/WatchlistEditor.php b/includes/WatchlistEditor.php
deleted file mode 100644 (file)
index f5359bc..0000000
+++ /dev/null
@@ -1,542 +0,0 @@
-<?php
-
-/**
- * Provides the UI through which users can perform editing
- * operations on their watchlist
- *
- * @ingroup Watchlist
- * @author Rob Church <robchur@gmail.com>
- */
-class WatchlistEditor {
-
-       /**
-        * Editing modes
-        */
-       const EDIT_CLEAR = 1;
-       const EDIT_RAW = 2;
-       const EDIT_NORMAL = 3;
-
-       /**
-        * Main execution point
-        *
-        * @param $user User
-        * @param $output OutputPage
-        * @param $request WebRequest
-        * @param $mode int
-        */
-       public function execute( $user, $output, $request, $mode ) {
-               global $wgUser, $wgLang;
-               if( wfReadOnly() ) {
-                       $output->readOnlyPage();
-                       return;
-               }
-               switch( $mode ) {
-                       case self::EDIT_CLEAR:
-                               // The "Clear" link scared people too much.
-                               // Pass on to the raw editor, from which it's very easy to clear.
-                       case self::EDIT_RAW:
-                               $output->setPageTitle( wfMsg( 'watchlistedit-raw-title' ) );
-                               if( $request->wasPosted() && $this->checkToken( $request, $wgUser ) ) {
-                                       $wanted = $this->extractTitles( $request->getText( 'titles' ) );
-                                       $current = $this->getWatchlist( $user );
-                                       if( count( $wanted ) > 0 ) {
-                                               $toWatch = array_diff( $wanted, $current );
-                                               $toUnwatch = array_diff( $current, $wanted );
-                                               $this->watchTitles( $toWatch, $user );
-                                               $this->unwatchTitles( $toUnwatch, $user );
-                                               $user->invalidateCache();
-                                               if( count( $toWatch ) > 0 || count( $toUnwatch ) > 0 )
-                                                       $output->addHTML( wfMsgExt( 'watchlistedit-raw-done', 'parse' ) );
-                                               if( ( $count = count( $toWatch ) ) > 0 ) {
-                                                       $output->addHTML( wfMsgExt( 'watchlistedit-raw-added', 'parse',
-                                                               $wgLang->formatNum( $count ) ) );
-                                                       $this->showTitles( $toWatch, $output, $wgUser->getSkin() );
-                                               }
-                                               if( ( $count = count( $toUnwatch ) ) > 0 ) {
-                                                       $output->addHTML( wfMsgExt( 'watchlistedit-raw-removed', 'parse',
-                                                               $wgLang->formatNum( $count ) ) );
-                                                       $this->showTitles( $toUnwatch, $output, $wgUser->getSkin() );
-                                               }
-                                       } else {
-                                               $this->clearWatchlist( $user );
-                                               $user->invalidateCache();
-                                               $output->addHTML( wfMsgExt( 'watchlistedit-raw-removed', 'parse',
-                                                       $wgLang->formatNum( count( $current ) ) ) );
-                                               $this->showTitles( $current, $output, $wgUser->getSkin() );
-                                       }
-                               }
-                               $this->showRawForm( $output, $user );
-                               break;
-                       case self::EDIT_NORMAL:
-                               $output->setPageTitle( wfMsg( 'watchlistedit-normal-title' ) );
-                               if( $request->wasPosted() && $this->checkToken( $request, $wgUser ) ) {
-                                       $titles = $this->extractTitles( $request->getArray( 'titles' ) );
-                                       $this->unwatchTitles( $titles, $user );
-                                       $user->invalidateCache();
-                                       $output->addHTML( wfMsgExt( 'watchlistedit-normal-done', 'parse',
-                                               $wgLang->formatNum( count( $titles ) ) ) );
-                                       $this->showTitles( $titles, $output, $wgUser->getSkin() );
-                               }
-                               $this->showNormalForm( $output, $user );
-               }
-       }
-
-       /**
-        * Check the edit token from a form submission
-        *
-        * @param $request WebRequest
-        * @param $user User
-        * @return bool
-        */
-       private function checkToken( $request, $user ) {
-               return $user->matchEditToken( $request->getVal( 'token' ), 'watchlistedit' );
-       }
-
-       /**
-        * Extract a list of titles from a blob of text, returning
-        * (prefixed) strings; unwatchable titles are ignored
-        *
-        * @param $list mixed
-        * @return array
-        */
-       private function extractTitles( $list ) {
-               $titles = array();
-               if( !is_array( $list ) ) {
-                       $list = explode( "\n", trim( $list ) );
-                       if( !is_array( $list ) ) {
-                               return array();
-                       }
-               }
-               foreach( $list as $text ) {
-                       $text = trim( $text );
-                       if( strlen( $text ) > 0 ) {
-                               $title = Title::newFromText( $text );
-                               if( $title instanceof Title && $title->isWatchable() ) {
-                                       $titles[] = $title->getPrefixedText();
-                               }
-                       }
-               }
-               return array_unique( $titles );
-       }
-
-       /**
-        * Print out a list of linked titles
-        *
-        * $titles can be an array of strings or Title objects; the former
-        * is preferred, since Titles are very memory-heavy
-        *
-        * @param $titles An array of strings, or Title objects
-        * @param $output OutputPage
-        * @param $skin Skin
-        */
-       private function showTitles( $titles, $output, $skin ) {
-               $talk = wfMsgHtml( 'talkpagelinktext' );
-               // Do a batch existence check
-               $batch = new LinkBatch();
-               foreach( $titles as $title ) {
-                       if( !$title instanceof Title ) {
-                               $title = Title::newFromText( $title );
-                       }
-                       if( $title instanceof Title ) {
-                               $batch->addObj( $title );
-                               $batch->addObj( $title->getTalkPage() );
-                       }
-               }
-               $batch->execute();
-               // Print out the list
-               $output->addHTML( "<ul>\n" );
-               foreach( $titles as $title ) {
-                       if( !$title instanceof Title ) {
-                               $title = Title::newFromText( $title );
-                       }
-                       if( $title instanceof Title ) {
-                               $output->addHTML( "<li>" . $skin->link( $title )
-                               . ' (' . $skin->link( $title->getTalkPage(), $talk ) . ")</li>\n" );
-                       }
-               }
-               $output->addHTML( "</ul>\n" );
-       }
-
-       /**
-        * Count the number of titles on a user's watchlist, excluding talk pages
-        *
-        * @param $user User
-        * @return int
-        */
-       private function countWatchlist( $user ) {
-               $dbr = wfGetDB( DB_MASTER );
-               $res = $dbr->select( 'watchlist', 'COUNT(*) AS count', array( 'wl_user' => $user->getId() ), __METHOD__ );
-               $row = $dbr->fetchObject( $res );
-               return ceil( $row->count / 2 ); // Paranoia
-       }
-
-       /**
-        * Prepare a list of titles on a user's watchlist (excluding talk pages)
-        * and return an array of (prefixed) strings
-        *
-        * @param $user User
-        * @return array
-        */
-       private function getWatchlist( $user ) {
-               $list = array();
-               $dbr = wfGetDB( DB_MASTER );
-               $res = $dbr->select(
-                       'watchlist',
-                       '*',
-                       array(
-                               'wl_user' => $user->getId(),
-                       ),
-                       __METHOD__
-               );
-               if( $res->numRows() > 0 ) {
-                       foreach ( $res as $row ) {
-                               $title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title );
-                               if( $title instanceof Title && !$title->isTalkPage() )
-                                       $list[] = $title->getPrefixedText();
-                       }
-                       $res->free();
-               }
-               return $list;
-       }
-
-       /**
-        * Get a list of titles on a user's watchlist, excluding talk pages,
-        * and return as a two-dimensional array with namespace, title and
-        * redirect status
-        *
-        * @param $user User
-        * @return array
-        */
-       private function getWatchlistInfo( $user ) {
-               $titles = array();
-               $dbr = wfGetDB( DB_MASTER );
-
-               $res = $dbr->select(
-                       array( 'watchlist', 'page' ),
-                       array(
-                               'wl_namespace',
-                               'wl_title',
-                               'page_id',
-                               'page_len',
-                               'page_is_redirect',
-                               'page_latest'
-                       ),
-                       array( 'wl_user' => $user->getId() ),
-                       __METHOD__,
-                       array( 'ORDER BY' => 'wl_namespace, wl_title' ),
-                       array( 'page' => array(
-                               'LEFT JOIN',
-                               'wl_namespace = page_namespace AND wl_title = page_title'
-                       ) )
-               );
-
-               if( $res && $dbr->numRows( $res ) > 0 ) {
-                       $cache = LinkCache::singleton();
-                       foreach ( $res as $row ) {
-                               $title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title );
-                               if( $title instanceof Title ) {
-                                       // Update the link cache while we're at it
-                                       if( $row->page_id ) {
-                                               $cache->addGoodLinkObj( $row->page_id, $title, $row->page_len, $row->page_is_redirect, $row->page_latest );
-                                       } else {
-                                               $cache->addBadLinkObj( $title );
-                                       }
-                                       // Ignore non-talk
-                                       if( !$title->isTalkPage() ) {
-                                               $titles[$row->wl_namespace][$row->wl_title] = $row->page_is_redirect;
-                                       }
-                               }
-                       }
-               }
-               return $titles;
-       }
-
-       /**
-        * Show a message indicating the number of items on the user's watchlist,
-        * and return this count for additional checking
-        *
-        * @param $output OutputPage
-        * @param $user User
-        * @return int
-        */
-       private function showItemCount( $output, $user ) {
-               if( ( $count = $this->countWatchlist( $user ) ) > 0 ) {
-                       $output->addHTML( wfMsgExt( 'watchlistedit-numitems', 'parse',
-                               $GLOBALS['wgLang']->formatNum( $count ) ) );
-               } else {
-                       $output->addHTML( wfMsgExt( 'watchlistedit-noitems', 'parse' ) );
-               }
-               return $count;
-       }
-
-       /**
-        * Remove all titles from a user's watchlist
-        *
-        * @param $user User
-        */
-       private function clearWatchlist( $user ) {
-               $dbw = wfGetDB( DB_MASTER );
-               $dbw->delete( 'watchlist', array( 'wl_user' => $user->getId() ), __METHOD__ );
-       }
-
-       /**
-        * Add a list of titles to a user's watchlist
-        *
-        * $titles can be an array of strings or Title objects; the former
-        * is preferred, since Titles are very memory-heavy
-        *
-        * @param $titles An array of strings, or Title objects
-        * @param $user User
-        */
-       private function watchTitles( $titles, $user ) {
-               $dbw = wfGetDB( DB_MASTER );
-               $rows = array();
-               foreach( $titles as $title ) {
-                       if( !$title instanceof Title ) {
-                               $title = Title::newFromText( $title );
-                       }
-                       if( $title instanceof Title ) {
-                               $rows[] = array(
-                                       'wl_user' => $user->getId(),
-                                       'wl_namespace' => ( $title->getNamespace() & ~1 ),
-                                       'wl_title' => $title->getDBkey(),
-                                       'wl_notificationtimestamp' => null,
-                               );
-                               $rows[] = array(
-                                       'wl_user' => $user->getId(),
-                                       'wl_namespace' => ( $title->getNamespace() | 1 ),
-                                       'wl_title' => $title->getDBkey(),
-                                       'wl_notificationtimestamp' => null,
-                               );
-                       }
-               }
-               $dbw->insert( 'watchlist', $rows, __METHOD__, 'IGNORE' );
-       }
-
-       /**
-        * Remove a list of titles from a user's watchlist
-        *
-        * $titles can be an array of strings or Title objects; the former
-        * is preferred, since Titles are very memory-heavy
-        *
-        * @param $titles An array of strings, or Title objects
-        * @param $user User
-        */
-       private function unwatchTitles( $titles, $user ) {
-               $dbw = wfGetDB( DB_MASTER );
-               foreach( $titles as $title ) {
-                       if( !$title instanceof Title ) {
-                               $title = Title::newFromText( $title );
-                       }
-                       if( $title instanceof Title ) {
-                               $dbw->delete(
-                                       'watchlist',
-                                       array(
-                                               'wl_user' => $user->getId(),
-                                               'wl_namespace' => ( $title->getNamespace() & ~1 ),
-                                               'wl_title' => $title->getDBkey(),
-                                       ),
-                                       __METHOD__
-                               );
-                               $dbw->delete(
-                                       'watchlist',
-                                       array(
-                                               'wl_user' => $user->getId(),
-                                               'wl_namespace' => ( $title->getNamespace() | 1 ),
-                                               'wl_title' => $title->getDBkey(),
-                                       ),
-                                       __METHOD__
-                               );
-                               $article = new Article($title);
-                               wfRunHooks('UnwatchArticleComplete',array(&$user,&$article));
-                       }
-               }
-       }
-
-       /**
-        * Show the standard watchlist editing form
-        *
-        * @param $output OutputPage
-        * @param $user User
-        */
-       private function showNormalForm( $output, $user ) {
-               global $wgUser;
-               $count = $this->showItemCount( $output, $user );
-               if( $count > 0 ) {
-                       $self = SpecialPage::getTitleFor( 'Watchlist' );
-                       $form  = Xml::openElement( 'form', array( 'method' => 'post',
-                               'action' => $self->getLocalUrl( array( 'action' => 'edit' ) ) ) );
-                       $form .= Html::hidden( 'token', $wgUser->editToken( 'watchlistedit' ) );
-                       $form .= "<fieldset>\n<legend>" . wfMsgHtml( 'watchlistedit-normal-legend' ) . "</legend>";
-                       $form .= wfMsgExt( 'watchlistedit-normal-explain', 'parse' );
-                       $form .= $this->buildRemoveList( $user, $wgUser->getSkin() );
-                       $form .= '<p>' . Xml::submitButton( wfMsg( 'watchlistedit-normal-submit' ) ) . '</p>';
-                       $form .= '</fieldset></form>';
-                       $output->addHTML( $form );
-               }
-       }
-
-       /**
-        * Build the part of the standard watchlist editing form with the actual
-        * title selection checkboxes and stuff.  Also generates a table of
-        * contents if there's more than one heading.
-        *
-        * @param $user User
-        * @param $skin Skin (really, Linker)
-        */
-       private function buildRemoveList( $user, $skin ) {
-               $list = "";
-               $toc = $skin->tocIndent();
-               $tocLength = 0;
-               foreach( $this->getWatchlistInfo( $user ) as $namespace => $pages ) {
-                       $tocLength++;
-                       $heading = htmlspecialchars( $this->getNamespaceHeading( $namespace ) );
-                       $anchor = "editwatchlist-ns" . $namespace;
-
-                       $list .= $skin->makeHeadLine( 2, ">", $anchor, $heading, "" );
-                       $toc .= $skin->tocLine( $anchor, $heading, $tocLength, 1 ) . $skin->tocLineEnd();
-
-                       $list .= "<ul>\n";
-                       foreach( $pages as $dbkey => $redirect ) {
-                               $title = Title::makeTitleSafe( $namespace, $dbkey );
-                               $list .= $this->buildRemoveLine( $title, $redirect, $skin );
-                       }
-                       $list .= "</ul>\n";
-               }
-               // ISSUE: omit the TOC if the total number of titles is low?
-               if( $tocLength > 1 ) {
-                       $list = $skin->tocList( $toc ) . $list;
-               }
-               return $list;
-       }
-
-       /**
-        * Get the correct "heading" for a namespace
-        *
-        * @param $namespace int
-        * @return string
-        */
-       private function getNamespaceHeading( $namespace ) {
-               return $namespace == NS_MAIN
-                       ? wfMsgHtml( 'blanknamespace' )
-                       : htmlspecialchars( $GLOBALS['wgContLang']->getFormattedNsText( $namespace ) );
-       }
-
-       /**
-        * Build a single list item containing a check box selecting a title
-        * and a link to that title, with various additional bits
-        *
-        * @param $title Title
-        * @param $redirect bool
-        * @param $skin Skin
-        * @return string
-        */
-       private function buildRemoveLine( $title, $redirect, $skin ) {
-               global $wgLang;
-
-               $link = $skin->link( $title );
-               if( $redirect ) {
-                       $link = '<span class="watchlistredir">' . $link . '</span>';
-               }
-               $tools[] = $skin->link( $title->getTalkPage(), wfMsgHtml( 'talkpagelinktext' ) );
-               if( $title->exists() ) {
-                       $tools[] = $skin->link(
-                               $title,
-                               wfMsgHtml( 'history_short' ),
-                               array(),
-                               array( 'action' => 'history' ),
-                               array( 'known', 'noclasses' )
-                       );
-               }
-               if( $title->getNamespace() == NS_USER && !$title->isSubpage() ) {
-                       $tools[] = $skin->link(
-                               SpecialPage::getTitleFor( 'Contributions', $title->getText() ),
-                               wfMsgHtml( 'contributions' ),
-                               array(),
-                               array(),
-                               array( 'known', 'noclasses' )
-                       );
-               }
-
-               wfRunHooks( 'WatchlistEditorBuildRemoveLine', array( &$tools, $title, $redirect, $skin ) );
-
-               return "<li>"
-                       . Xml::check( 'titles[]', false, array( 'value' => $title->getPrefixedText() ) )
-                       . $link . " (" . $wgLang->pipeList( $tools ) . ")" . "</li>\n";
-       }
-
-       /**
-        * Show a form for editing the watchlist in "raw" mode
-        *
-        * @param $output OutputPage
-        * @param $user User
-        */
-       public function showRawForm( $output, $user ) {
-               global $wgUser;
-               $this->showItemCount( $output, $user );
-               $self = SpecialPage::getTitleFor( 'Watchlist' );
-               $form = Xml::openElement( 'form', array( 'method' => 'post',
-                       'action' => $self->getLocalUrl( array( 'action' => 'raw' ) ) ) );
-               $form .= Html::hidden( 'token', $wgUser->editToken( 'watchlistedit' ) );
-               $form .= '<fieldset><legend>' . wfMsgHtml( 'watchlistedit-raw-legend' ) . '</legend>';
-               $form .= wfMsgExt( 'watchlistedit-raw-explain', 'parse' );
-               $form .= Xml::label( wfMsg( 'watchlistedit-raw-titles' ), 'titles' );
-               $form .= "<br />\n";
-               $form .= Xml::openElement( 'textarea', array( 'id' => 'titles', 'name' => 'titles',
-                       'rows' => $wgUser->getIntOption( 'rows' ), 'cols' => $wgUser->getIntOption( 'cols' ) ) );
-               $titles = $this->getWatchlist( $user );
-               foreach( $titles as $title ) {
-                       $form .= htmlspecialchars( $title ) . "\n";
-               }
-               $form .= '</textarea>';
-               $form .= '<p>' . Xml::submitButton( wfMsg( 'watchlistedit-raw-submit' ) ) . '</p>';
-               $form .= '</fieldset></form>';
-               $output->addHTML( $form );
-       }
-
-       /**
-        * Determine whether we are editing the watchlist, and if so, what
-        * kind of editing operation
-        *
-        * @param $request WebRequest
-        * @param $par mixed
-        * @return int
-        */
-       public static function getMode( $request, $par ) {
-               $mode = strtolower( $request->getVal( 'action', $par ) );
-               switch( $mode ) {
-                       case 'clear':
-                               return self::EDIT_CLEAR;
-                       case 'raw':
-                               return self::EDIT_RAW;
-                       case 'edit':
-                               return self::EDIT_NORMAL;
-                       default:
-                               return false;
-               }
-       }
-
-       /**
-        * Build a set of links for convenient navigation
-        * between watchlist viewing and editing modes
-        *
-        * @param $skin Skin to use
-        * @return string
-        */
-       public static function buildTools( $skin ) {
-               global $wgLang;
-
-               $tools = array();
-               $modes = array( 'view' => false, 'edit' => 'edit', 'raw' => 'raw' );
-               foreach( $modes as $mode => $subpage ) {
-                       // can use messages 'watchlisttools-view', 'watchlisttools-edit', 'watchlisttools-raw'
-                       $tools[] = $skin->linkKnown(
-                               SpecialPage::getTitleFor( 'Watchlist', $subpage ),
-                               wfMsgHtml( "watchlisttools-{$mode}" )
-                       );
-               }
-               return Html::rawElement( 'span',
-                                       array( 'class' => 'mw-watchlist-toollinks' ),
-                                       wfMsg( 'parentheses', $wgLang->pipeList( $tools ) ) );
-       }
-}
diff --git a/includes/specials/SpecialEditWatchlist.php b/includes/specials/SpecialEditWatchlist.php
new file mode 100644 (file)
index 0000000..668cdc2
--- /dev/null
@@ -0,0 +1,603 @@
+<?php
+
+/**
+ * Provides the UI through which users can perform editing
+ * operations on their watchlist
+ *
+ * @ingroup Watchlist
+ * @author Rob Church <robchur@gmail.com>
+ */
+class SpecialEditWatchlist extends UnlistedSpecialPage {
+
+       /**
+        * Editing modes
+        */
+       const EDIT_CLEAR = 1;
+       const EDIT_RAW = 2;
+       const EDIT_NORMAL = 3;
+
+       protected $successMessage;
+
+       public function __construct(){
+               parent::__construct( 'EditWatchlist' );
+       }
+
+       /**
+        * Main execution point
+        *
+        * @param $user User
+        * @param $output OutputPage
+        * @param $request WebRequest
+        * @param $mode int
+        */
+       public function execute( $mode ) {
+               global $wgUser, $wgLang, $wgOut, $wgRequest;
+               if( wfReadOnly() ) {
+                       $wgOut->readOnlyPage();
+                       return;
+               }
+
+               # Anons don't get a watchlist
+               if( $wgUser->isAnon() ) {
+                       $wgOut->setPageTitle( wfMsg( 'watchnologin' ) );
+                       $llink = $wgUser->getSkin()->linkKnown(
+                               SpecialPage::getTitleFor( 'Userlogin' ),
+                               wfMsgHtml( 'loginreqlink' ),
+                               array(),
+                               array( 'returnto' => $this->getTitle()->getPrefixedText() )
+                       );
+                       $wgOut->addWikiMsgArray( 'watchlistanontext', array( $llink ), array( 'replaceafter' ) );
+                       return;
+               }
+
+               $sub  = wfMsgExt(
+                       'watchlistfor2',
+                       array( 'parseinline', 'replaceafter' ),
+                       $wgUser->getName(),
+                       SpecialEditWatchlist::buildTools( $wgUser->getSkin() )
+               );
+               $wgOut->setSubtitle( $sub );
+
+               # B/C: $mode used to be waaay down the parameter list, and the first parameter
+               # was $wgUser
+               if( $mode instanceof User ){
+                       $args = func_get_args();
+                       if( count( $args >= 4 ) ){
+                               $mode = $args[3];
+                       }
+               }
+               $mode = self::getMode( $wgRequest, $mode );
+
+               switch( $mode ) {
+                       case self::EDIT_CLEAR:
+                               // The "Clear" link scared people too much.
+                               // Pass on to the raw editor, from which it's very easy to clear.
+
+                       case self::EDIT_RAW:
+                               $wgOut->setPageTitle( wfMsg( 'watchlistedit-raw-title' ) );
+                               $form = $this->getRawForm( $wgUser );
+                               if( $form->show() ){
+                                       $wgOut->addHTML( $this->successMessage );
+                                       $wgOut->returnToMain();
+                               }
+                               break;
+
+                       case self::EDIT_NORMAL:
+                       default:
+                               $wgOut->setPageTitle( wfMsg( 'watchlistedit-normal-title' ) );
+                               $form = $this->getNormalForm( $wgUser );
+                               if( $form->show() ){
+                                       $wgOut->addHTML( $this->successMessage );
+                                       $wgOut->returnToMain();
+                               }
+                               break;
+               }
+       }
+
+       /**
+        * Extract a list of titles from a blob of text, returning
+        * (prefixed) strings; unwatchable titles are ignored
+        *
+        * @param $list String
+        * @return array
+        */
+       private function extractTitles( $list ) {
+               $titles = array();
+               $list = explode( "\n", trim( $list ) );
+               if( !is_array( $list ) ) {
+                       return array();
+               }
+               foreach( $list as $text ) {
+                       $text = trim( $text );
+                       if( strlen( $text ) > 0 ) {
+                               $title = Title::newFromText( $text );
+                               if( $title instanceof Title && $title->isWatchable() ) {
+                                       $titles[] = $title->getPrefixedText();
+                               }
+                       }
+               }
+               return array_unique( $titles );
+       }
+
+       public function submitRaw( $data ){
+               global $wgUser, $wgLang;
+               $wanted = $this->extractTitles( $data['Titles'] );
+               $current = $this->getWatchlist( $wgUser );
+
+               if( count( $wanted ) > 0 ) {
+                       $toWatch = array_diff( $wanted, $current );
+                       $toUnwatch = array_diff( $current, $wanted );
+                       $this->watchTitles( $toWatch, $wgUser );
+                       $this->unwatchTitles( $toUnwatch, $wgUser );
+                       $wgUser->invalidateCache();
+
+                       if( count( $toWatch ) > 0 || count( $toUnwatch ) > 0 ){
+                               $this->successMessage = wfMessage( 'watchlistedit-raw-done' )->parse();
+                       } else {
+                               return false;
+                       }
+
+                       if( count( $toWatch ) > 0 ) {
+                               $this->successMessage .= wfMessage(
+                                       'watchlistedit-raw-added',
+                                       $wgLang->formatNum( count( $toWatch ) )
+                               );
+                               $this->showTitles( $toWatch, $this->successMessage, $wgUser->getSkin() );
+                       }
+
+                       if( count( $toUnwatch ) > 0 ) {
+                               $this->successMessage .= wfMessage(
+                                       'watchlistedit-raw-removed',
+                                       $wgLang->formatNum( count( $toUnwatch ) )
+                               );
+                               $this->showTitles( $toUnwatch, $this->successMessage, $wgUser->getSkin() );
+                       }
+               } else {
+                       $this->clearWatchlist( $wgUser );
+                       $wgUser->invalidateCache();
+                       $this->successMessage .= wfMessage(
+                               'watchlistedit-raw-removed',
+                               $wgLang->formatNum( count( $current ) )
+                       );
+                       $this->showTitles( $current, $this->successMessage, $wgUser->getSkin() );
+               }
+               return true;
+       }
+
+       /**
+        * Print out a list of linked titles
+        *
+        * $titles can be an array of strings or Title objects; the former
+        * is preferred, since Titles are very memory-heavy
+        *
+        * @param $titles array of strings, or Title objects
+        * @param $output String
+        * @param $skin Skin
+        */
+       private function showTitles( $titles, &$output, $skin ) {
+               $talk = wfMsgHtml( 'talkpagelinktext' );
+               // Do a batch existence check
+               $batch = new LinkBatch();
+               foreach( $titles as $title ) {
+                       if( !$title instanceof Title ) {
+                               $title = Title::newFromText( $title );
+                       }
+                       if( $title instanceof Title ) {
+                               $batch->addObj( $title );
+                               $batch->addObj( $title->getTalkPage() );
+                       }
+               }
+               $batch->execute();
+               // Print out the list
+               $output .= "<ul>\n";
+               foreach( $titles as $title ) {
+                       if( !$title instanceof Title ) {
+                               $title = Title::newFromText( $title );
+                       }
+                       if( $title instanceof Title ) {
+                               $output .= "<li>"
+                                       . $skin->link( $title )
+                                       . ' (' . $skin->link( $title->getTalkPage(), $talk )
+                                       . ")</li>\n";
+                       }
+               }
+               $output .= "</ul>\n";
+       }
+
+       /**
+        * Count the number of titles on a user's watchlist, excluding talk pages
+        *
+        * @param $user User
+        * @return int
+        */
+       private function countWatchlist( $user ) {
+               $dbr = wfGetDB( DB_MASTER );
+               $res = $dbr->select( 'watchlist', 'COUNT(*) AS count', array( 'wl_user' => $user->getId() ), __METHOD__ );
+               $row = $dbr->fetchObject( $res );
+               return ceil( $row->count / 2 ); // Paranoia
+       }
+
+       /**
+        * Prepare a list of titles on a user's watchlist (excluding talk pages)
+        * and return an array of (prefixed) strings
+        *
+        * @param $user User
+        * @return array
+        */
+       private function getWatchlist( $user ) {
+               $list = array();
+               $dbr = wfGetDB( DB_MASTER );
+               $res = $dbr->select(
+                       'watchlist',
+                       '*',
+                       array(
+                               'wl_user' => $user->getId(),
+                       ),
+                       __METHOD__
+               );
+               if( $res->numRows() > 0 ) {
+                       foreach ( $res as $row ) {
+                               $title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title );
+                               if( $title instanceof Title && !$title->isTalkPage() )
+                                       $list[] = $title->getPrefixedText();
+                       }
+                       $res->free();
+               }
+               return $list;
+       }
+
+       /**
+        * Get a list of titles on a user's watchlist, excluding talk pages,
+        * and return as a two-dimensional array with namespace, title and
+        * redirect status
+        *
+        * @param $user User
+        * @return array
+        */
+       private function getWatchlistInfo( $user ) {
+               $titles = array();
+               $dbr = wfGetDB( DB_MASTER );
+
+               $res = $dbr->select(
+                       array( 'watchlist', 'page' ),
+                       array(
+                               'wl_namespace',
+                               'wl_title',
+                               'page_id',
+                               'page_len',
+                               'page_is_redirect',
+                               'page_latest'
+                       ),
+                       array( 'wl_user' => $user->getId() ),
+                       __METHOD__,
+                       array( 'ORDER BY' => 'wl_namespace, wl_title' ),
+                       array( 'page' => array(
+                               'LEFT JOIN',
+                               'wl_namespace = page_namespace AND wl_title = page_title'
+                       ) )
+               );
+
+               if( $res && $dbr->numRows( $res ) > 0 ) {
+                       $cache = LinkCache::singleton();
+                       foreach ( $res as $row ) {
+                               $title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title );
+                               if( $title instanceof Title ) {
+                                       // Update the link cache while we're at it
+                                       if( $row->page_id ) {
+                                               $cache->addGoodLinkObj( $row->page_id, $title, $row->page_len, $row->page_is_redirect, $row->page_latest );
+                                       } else {
+                                               $cache->addBadLinkObj( $title );
+                                       }
+                                       // Ignore non-talk
+                                       if( !$title->isTalkPage() ) {
+                                               $titles[$row->wl_namespace][$row->wl_title] = $row->page_is_redirect;
+                                       }
+                               }
+                       }
+               }
+               return $titles;
+       }
+
+       /**
+        * Show a message indicating the number of items on the user's watchlist,
+        * and return this count for additional checking
+        *
+        * @param $output OutputPage
+        * @param $user User
+        * @return int
+        */
+       private function showItemCount( $output, $user ) {
+               if( ( $count = $this->countWatchlist( $user ) ) > 0 ) {
+                       $output->addHTML( wfMsgExt( 'watchlistedit-numitems', 'parse',
+                               $GLOBALS['wgLang']->formatNum( $count ) ) );
+               } else {
+                       $output->addHTML( wfMsgExt( 'watchlistedit-noitems', 'parse' ) );
+               }
+               return $count;
+       }
+
+       /**
+        * Remove all titles from a user's watchlist
+        *
+        * @param $user User
+        */
+       private function clearWatchlist( $user ) {
+               $dbw = wfGetDB( DB_MASTER );
+               $dbw->delete(
+                       'watchlist',
+                       array( 'wl_user' => $user->getId() ),
+                       __METHOD__
+               );
+       }
+
+       /**
+        * Add a list of titles to a user's watchlist
+        *
+        * $titles can be an array of strings or Title objects; the former
+        * is preferred, since Titles are very memory-heavy
+        *
+        * @param $titles Array of strings, or Title objects
+        * @param $user User
+        */
+       private function watchTitles( $titles, $user ) {
+               $dbw = wfGetDB( DB_MASTER );
+               $rows = array();
+               foreach( $titles as $title ) {
+                       if( !$title instanceof Title ) {
+                               $title = Title::newFromText( $title );
+                       }
+                       if( $title instanceof Title ) {
+                               $rows[] = array(
+                                       'wl_user' => $user->getId(),
+                                       'wl_namespace' => ( $title->getNamespace() & ~1 ),
+                                       'wl_title' => $title->getDBkey(),
+                                       'wl_notificationtimestamp' => null,
+                               );
+                               $rows[] = array(
+                                       'wl_user' => $user->getId(),
+                                       'wl_namespace' => ( $title->getNamespace() | 1 ),
+                                       'wl_title' => $title->getDBkey(),
+                                       'wl_notificationtimestamp' => null,
+                               );
+                       }
+               }
+               $dbw->insert( 'watchlist', $rows, __METHOD__, 'IGNORE' );
+       }
+
+       /**
+        * Remove a list of titles from a user's watchlist
+        *
+        * $titles can be an array of strings or Title objects; the former
+        * is preferred, since Titles are very memory-heavy
+        *
+        * @param $titles Array of strings, or Title objects
+        * @param $user User
+        */
+       private function unwatchTitles( $titles, $user ) {
+               $dbw = wfGetDB( DB_MASTER );
+               foreach( $titles as $title ) {
+                       if( !$title instanceof Title ) {
+                               $title = Title::newFromText( $title );
+                       }
+                       if( $title instanceof Title ) {
+                               $dbw->delete(
+                                       'watchlist',
+                                       array(
+                                               'wl_user' => $user->getId(),
+                                               'wl_namespace' => ( $title->getNamespace() & ~1 ),
+                                               'wl_title' => $title->getDBkey(),
+                                       ),
+                                       __METHOD__
+                               );
+                               $dbw->delete(
+                                       'watchlist',
+                                       array(
+                                               'wl_user' => $user->getId(),
+                                               'wl_namespace' => ( $title->getNamespace() | 1 ),
+                                               'wl_title' => $title->getDBkey(),
+                                       ),
+                                       __METHOD__
+                               );
+                               $article = new Article($title);
+                               wfRunHooks('UnwatchArticleComplete',array(&$user,&$article));
+                       }
+               }
+       }
+
+       public function submitNormal( $data ) {
+               global $wgUser;
+               $removed = array();
+
+               foreach( $data as $titles ) {
+                       $this->unwatchTitles( $titles, $wgUser );
+                       $removed += $titles;
+               }
+
+               if( count( $removed ) > 0 ) {
+                       global $wgLang;
+                       $this->successMessage = wfMessage(
+                               'watchlistedit-normal-done',
+                               $wgLang->formatNum( count( $removed ) )
+                       );
+                       $this->showTitles( $removed, $this->successMessage, $wgUser->getSkin() );
+                       return true;
+               } else {
+                       return false;
+               }
+       }
+
+       /**
+        * Get the standard watchlist editing form
+        *
+        * @param $user User
+        * @return HTMLForm
+        */
+       protected function getNormalForm( $user ){
+               global $wgContLang;
+               $skin = $user->getSkin();
+               $fields = array();
+
+               foreach( $this->getWatchlistInfo( $user ) as $namespace => $pages ){
+
+                       $namespace == NS_MAIN
+                               ? wfMsgHtml( 'blanknamespace' )
+                               : htmlspecialchars( $wgContLang->getFormattedNsText( $namespace ) );
+
+                       $fields['TitlesNs'.$namespace] = array(
+                               'type' => 'multiselect',
+                               'options' => array(),
+                               'section' => "ns$namespace",
+                       );
+
+                       foreach( $pages as $dbkey => $redirect ){
+                               $title = Title::makeTitleSafe( $namespace, $dbkey );
+                               $text = $this->buildRemoveLine( $title, $redirect, $skin );
+                               $fields['TitlesNs'.$namespace]['options'][$text] = $title->getEscapedText();
+                       }
+               }
+
+               $form = new EditWatchlistNormalHTMLForm( $fields );
+               $form->setTitle( $this->getTitle() );
+               $form->setSubmitText( wfMessage( 'watchlistedit-normal-submit' )->text() );
+               $form->setWrapperLegend( wfMessage( 'watchlistedit-normal-legend' )->text() );
+               $form->addHeaderText( wfMessage( 'watchlistedit-normal-explain' )->parse() );
+               $form->setSubmitCallback( array( $this, 'submitNormal' ) );
+               return $form;
+       }
+
+       /**
+        * Build the label for a checkbox, with a link to the title, and various additional bits
+        *
+        * @param $title Title
+        * @param $redirect bool
+        * @param $skin Skin
+        * @return string
+        */
+       private function buildRemoveLine( $title, $redirect, $skin ) {
+               global $wgLang;
+
+               $link = $skin->link( $title );
+               if( $redirect ) {
+                       $link = '<span class="watchlistredir">' . $link . '</span>';
+               }
+               $tools[] = $skin->link( $title->getTalkPage(), wfMsgHtml( 'talkpagelinktext' ) );
+               if( $title->exists() ) {
+                       $tools[] = $skin->link(
+                               $title,
+                               wfMsgHtml( 'history_short' ),
+                               array(),
+                               array( 'action' => 'history' ),
+                               array( 'known', 'noclasses' )
+                       );
+               }
+               if( $title->getNamespace() == NS_USER && !$title->isSubpage() ) {
+                       $tools[] = $skin->link(
+                               SpecialPage::getTitleFor( 'Contributions', $title->getText() ),
+                               wfMsgHtml( 'contributions' ),
+                               array(),
+                               array(),
+                               array( 'known', 'noclasses' )
+                       );
+               }
+
+               wfRunHooks( 'WatchlistEditorBuildRemoveLine', array( &$tools, $title, $redirect, $skin ) );
+
+               return $link . " (" . $wgLang->pipeList( $tools ) . ")";
+       }
+
+       /**
+        * Get a form for editing the watchlist in "raw" mode
+        *
+        * @param $user User
+        * @return HTMLForm
+        */
+       protected function getRawForm( $user ){
+               $titles = implode( array_map( 'htmlspecialchars', $this->getWatchlist( $user ) ), "\n" );
+               $fields = array(
+                       'Titles' => array(
+                               'type' => 'textarea',
+                               'label-message' => 'watchlistedit-raw-titles',
+                               'default' => $titles,
+                       ),
+               );
+               $form = new HTMLForm( $fields );
+               $form->setTitle( $this->getTitle( 'raw' ) );
+               $form->setSubmitText( wfMessage( 'watchlistedit-raw-submit' )->text() );
+               $form->setWrapperLegend( wfMessage( 'watchlistedit-raw-legend' )->text() );
+               $form->addHeaderText( wfMessage( 'watchlistedit-raw-explain' )->parse() );
+               $form->setSubmitCallback( array( $this, 'submitRaw' ) );
+               return $form;
+       }
+
+       /**
+        * Determine whether we are editing the watchlist, and if so, what
+        * kind of editing operation
+        *
+        * @param $request WebRequest
+        * @param $par mixed
+        * @return int
+        */
+       public static function getMode( $request, $par ) {
+               $mode = strtolower( $request->getVal( 'action', $par ) );
+               switch( $mode ) {
+                       case 'clear':
+                       case self::EDIT_CLEAR:
+                               return self::EDIT_CLEAR;
+
+                       case 'raw':
+                       case self::EDIT_RAW:
+                               return self::EDIT_RAW;
+
+                       case 'edit':
+                       case self::EDIT_NORMAL:
+                               return self::EDIT_NORMAL;
+
+                       default:
+                               return false;
+               }
+       }
+
+       /**
+        * Build a set of links for convenient navigation
+        * between watchlist viewing and editing modes
+        *
+        * @param $skin Skin to use
+        * @return string
+        */
+       public static function buildTools( $skin ) {
+               global $wgLang;
+
+               $tools = array();
+               $modes = array(
+                       'view' => array( 'Watchlist', false ),
+                       'edit' => array( 'EditWatchlist', false ),
+                       'raw' => array( 'EditWatchlist', 'raw' ),
+               );
+               foreach( $modes as $mode => $arr ) {
+                       // can use messages 'watchlisttools-view', 'watchlisttools-edit', 'watchlisttools-raw'
+                       $tools[] = $skin->linkKnown(
+                               SpecialPage::getTitleFor( $arr[0], $arr[1] ),
+                               wfMsgHtml( "watchlisttools-{$mode}" )
+                       );
+               }
+               return Html::rawElement( 'span',
+                                       array( 'class' => 'mw-watchlist-toollinks' ),
+                                       wfMsg( 'parentheses', $wgLang->pipeList( $tools ) ) );
+       }
+}
+
+# B/C since 1.18
+class WatchlistEditor extends SpecialEditWatchlist {}
+
+/**
+ * Extend HTMLForm purely so we can have a more sane way of getting the section headers
+ */
+class EditWatchlistNormalHTMLForm extends HTMLForm {
+       public function getLegend( $namespace ){
+               global $wgLang;
+               $namespace = substr( $namespace, 2 );
+               return $namespace == NS_MAIN
+                       ? wfMsgHtml( 'blanknamespace' )
+                       : htmlspecialchars( $wgLang->getFormattedNsText( $namespace ) );
+       }
+}
\ No newline at end of file
index ecda239..771a511 100644 (file)
@@ -68,12 +68,28 @@ function wfSpecialWatchlist( $par ) {
 
        $wgOut->setPageTitle( wfMsg( 'watchlist' ) );
 
-       $sub  = wfMsgExt( 'watchlistfor2', array( 'parseinline', 'replaceafter' ), $wgUser->getName(), WatchlistEditor::buildTools( $wgUser->getSkin() ) );
+       $sub  = wfMsgExt(
+               'watchlistfor2',
+               array( 'parseinline', 'replaceafter' ),
+               $wgUser->getName(),
+               SpecialEditWatchlist::buildTools( $wgUser->getSkin() )
+       );
        $wgOut->setSubtitle( $sub );
 
-       if( ( $mode = WatchlistEditor::getMode( $wgRequest, $par ) ) !== false ) {
-               $editor = new WatchlistEditor();
-               $editor->execute( $wgUser, $wgOut, $wgRequest, $mode );
+       if( ( $mode = SpecialEditWatchlist::getMode( $wgRequest, $par ) ) !== false ) {
+               # TODO: localise?
+               switch( $mode ){
+                       case SpecialEditWatchlist::EDIT_CLEAR:
+                               $mode = 'clear';
+                               break;
+                       case SpecialEditWatchlist::EDIT_RAW:
+                               $mode = 'raw';
+                               break;
+                       default:
+                               $mode = null;
+               }
+               $title = SpecialPage::getTitleFor( 'EditWatchlist', $mode );
+               $wgOut->redirect( $title->getLocalUrl() );
                return;
        }
 
index 528d03e..d93e322 100644 (file)
@@ -398,6 +398,7 @@ $specialPageAliases = array(
        'DisableAccount'            => array( 'DisableAccount' ),
        'Disambiguations'           => array( 'Disambiguations' ),
        'DoubleRedirects'           => array( 'DoubleRedirects' ),
+       'EditWatchlist'             => array( 'EditWatchlist' ),
        'Emailuser'                 => array( 'EmailUser' ),
        'Export'                    => array( 'Export' ),
        'Fewestrevisions'           => array( 'FewestRevisions' ),
@@ -4234,7 +4235,7 @@ Try normal preview.',
 'watchlistedit-normal-legend'  => 'Remove titles from watchlist',
 'watchlistedit-normal-explain' => 'Titles on your watchlist are shown below.
 To remove a title, check the box next to it, and click "{{int:Watchlistedit-normal-submit}}".
-You can also [[Special:Watchlist/raw|edit the raw list]].',
+You can also [[Special:EditWatchlist/raw|edit the raw list]].',
 'watchlistedit-normal-submit'  => 'Remove titles',
 'watchlistedit-normal-done'    => '{{PLURAL:$1|1 title was|$1 titles were}} removed from your watchlist:',
 'watchlistedit-raw-title'      => 'Edit raw watchlist',
@@ -4242,7 +4243,7 @@ You can also [[Special:Watchlist/raw|edit the raw list]].',
 'watchlistedit-raw-explain'    => 'Titles on your watchlist are shown below, and can be edited by adding to and removing from the list;
 one title per line.
 When finished, click "{{int:Watchlistedit-raw-submit}}".
-You can also [[Special:Watchlist/edit|use the standard editor]].',
+You can also [[Special:EditWatchlist|use the standard editor]].',
 'watchlistedit-raw-titles'     => 'Titles:',
 'watchlistedit-raw-submit'     => 'Update watchlist',
 'watchlistedit-raw-done'       => 'Your watchlist has been updated.',