]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/WatchlistEditor.php
MediaWiki 1.11.0
[autoinstallsdev/mediawiki.git] / includes / WatchlistEditor.php
1 <?php
2
3 /**
4  * Provides the UI through which users can perform editing
5  * operations on their watchlist
6  *
7  * @addtogroup Watchlist
8  * @author Rob Church <robchur@gmail.com>
9  */
10 class WatchlistEditor {
11
12         /**
13          * Editing modes
14          */
15         const EDIT_CLEAR = 1;
16         const EDIT_RAW = 2;
17         const EDIT_NORMAL = 3;
18
19         /**
20          * Main execution point
21          *
22          * @param User $user
23          * @param OutputPage $output
24          * @param WebRequest $request
25          * @param int $mode
26          */
27         public function execute( $user, $output, $request, $mode ) {
28                 global $wgUser;
29                 if( wfReadOnly() ) {
30                         $output->readOnlyPage();
31                         return;
32                 }
33                 switch( $mode ) {
34                         case self::EDIT_CLEAR:
35                                 $output->setPageTitle( wfMsg( 'watchlistedit-clear-title' ) );
36                                 if( $request->wasPosted() && $this->checkToken( $request, $wgUser ) ) {
37                                         $this->clearWatchlist( $user );
38                                         $user->invalidateCache();
39                                         $output->addHtml( wfMsgExt( 'watchlistedit-clear-done', 'parse' ) );
40                                 } else {
41                                         $this->showClearForm( $output, $user );
42                                 }
43                                 break;
44                         case self::EDIT_RAW:
45                                 $output->setPageTitle( wfMsg( 'watchlistedit-raw-title' ) );
46                                 if( $request->wasPosted() && $this->checkToken( $request, $wgUser ) ) {
47                                         $wanted = $this->extractTitles( $request->getText( 'titles' ) );
48                                         $current = $this->getWatchlist( $user );
49                                         if( count( $wanted ) > 0 ) {
50                                                 $toWatch = array_diff( $wanted, $current );
51                                                 $toUnwatch = array_diff( $current, $wanted );
52                                                 $this->watchTitles( $toWatch, $user );
53                                                 $this->unwatchTitles( $toUnwatch, $user );
54                                                 $user->invalidateCache();
55                                                 if( count( $toWatch ) > 0 || count( $toUnwatch ) > 0 )
56                                                         $output->addHtml( wfMsgExt( 'watchlistedit-raw-done', 'parse' ) );
57                                                 if( ( $count = count( $toWatch ) ) > 0 ) {
58                                                         $output->addHtml( wfMsgExt( 'watchlistedit-raw-added', 'parse', $count ) );
59                                                         $this->showTitles( $toWatch, $output, $wgUser->getSkin() );
60                                                 }
61                                                 if( ( $count = count( $toUnwatch ) ) > 0 ) {
62                                                         $output->addHtml( wfMsgExt( 'watchlistedit-raw-removed', 'parse', $count ) );
63                                                         $this->showTitles( $toUnwatch, $output, $wgUser->getSkin() );
64                                                 }
65                                         } else {
66                                                 $this->clearWatchlist( $user );
67                                                 $user->invalidateCache();
68                                                 $output->addHtml( wfMsgExt( 'watchlistedit-raw-removed', 'parse', count( $current ) ) );
69                                                 $this->showTitles( $current, $output, $wgUser->getSkin() );
70                                         }
71                                 }
72                                 $this->showRawForm( $output, $user );
73                                 break;
74                         case self::EDIT_NORMAL:
75                                 $output->setPageTitle( wfMsg( 'watchlistedit-normal-title' ) );
76                                 if( $request->wasPosted() && $this->checkToken( $request, $wgUser ) ) {
77                                         $titles = $this->extractTitles( $request->getArray( 'titles' ) );
78                                         $this->unwatchTitles( $titles, $user );
79                                         $user->invalidateCache();
80                                         $output->addHtml( wfMsgExt( 'watchlistedit-normal-done', 'parse',
81                                                 $GLOBALS['wgLang']->formatNum( count( $titles ) ) ) );
82                                         $this->showTitles( $titles, $output, $wgUser->getSkin() );
83                                 }
84                                 $this->showNormalForm( $output, $user );
85                 }
86         }
87         
88         /**
89          * Check the edit token from a form submission
90          *
91          * @param WebRequest $request
92          * @param User $user
93          * @return bool
94          */
95         private function checkToken( $request, $user ) {
96                 return $user->matchEditToken( $request->getVal( 'token' ), 'watchlistedit' );   
97         }
98         
99         /**
100          * Extract a list of titles from a blob of text, returning
101          * (prefixed) strings; unwatchable titles are ignored
102          *
103          * @param mixed $list
104          * @return array
105          */
106         private function extractTitles( $list ) {
107                 $titles = array();
108                 if( !is_array( $list ) ) {
109                         $list = explode( "\n", trim( $list ) );
110                         if( !is_array( $list ) )
111                                 return array();
112                 }
113                 foreach( $list as $text ) {
114                         $text = trim( $text );
115                         if( strlen( $text ) > 0 ) {
116                                 $title = Title::newFromText( $text );
117                                 if( $title instanceof Title && $title->isWatchable() )
118                                         $titles[] = $title->getPrefixedText();
119                         }
120                 }
121                 return array_unique( $titles );
122         }
123         
124         /**
125          * Print out a list of linked titles
126          *
127          * $titles can be an array of strings or Title objects; the former
128          * is preferred, since Titles are very memory-heavy
129          *
130          * @param array $titles An array of strings, or Title objects
131          * @param OutputPage $output
132          * @param Skin $skin
133          */
134         private function showTitles( $titles, $output, $skin ) {
135                 $talk = wfMsgHtml( 'talkpagelinktext' );
136                 // Do a batch existence check           
137                 $batch = new LinkBatch();
138                 foreach( $titles as $title ) {
139                         if( !$title instanceof Title )
140                                 $title = Title::newFromText( $title );
141                         if( $title instanceof Title ) {
142                                 $batch->addObj( $title );
143                                 $batch->addObj( $title->getTalkPage() );
144                         }
145                 }
146                 $batch->execute();
147                 // Print out the list
148                 $output->addHtml( "<ul>\n" );
149                 foreach( $titles as $title ) {
150                         if( !$title instanceof Title )
151                                 $title = Title::newFromText( $title );
152                         if( $title instanceof Title ) {
153                                 $output->addHtml( "<li>" . $skin->makeLinkObj( $title )
154                                 . ' (' . $skin->makeLinkObj( $title->getTalkPage(), $talk ) . ")</li>\n" );
155                         }
156                 }
157                 $output->addHtml( "</ul>\n" );
158         }
159         
160         /**
161          * Count the number of titles on a user's watchlist, excluding talk pages
162          *
163          * @param User $user
164          * @return int
165          */
166         private function countWatchlist( $user ) {
167                 $dbr = wfGetDB( DB_MASTER );
168                 $res = $dbr->select( 'watchlist', 'COUNT(*) AS count', array( 'wl_user' => $user->getId() ), __METHOD__ );
169                 $row = $dbr->fetchObject( $res );
170                 return ceil( $row->count / 2 ); // Paranoia
171         }
172         
173         /**
174          * Prepare a list of titles on a user's watchlist (excluding talk pages)
175          * and return an array of (prefixed) strings
176          *
177          * @param User $user
178          * @return array
179          */
180         private function getWatchlist( $user ) {
181                 $list = array();        
182                 $dbr = wfGetDB( DB_MASTER );
183                 $res = $dbr->select(
184                         'watchlist',
185                         '*',
186                         array(
187                                 'wl_user' => $user->getId(),
188                         ),
189                         __METHOD__
190                 );
191                 if( $res->numRows() > 0 ) {
192                         while( $row = $res->fetchObject() ) {
193                                 $title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title );
194                                 if( $title instanceof Title && !$title->isTalkPage() )
195                                         $list[] = $title->getPrefixedText();
196                         }
197                         $res->free();           
198                 }
199                 return $list;
200         }
201         
202         /**
203          * Get a list of titles on a user's watchlist, excluding talk pages,
204          * and return as a two-dimensional array with namespace, title and
205          * redirect status
206          *
207          * @param User $user
208          * @return array
209          */
210         private function getWatchlistInfo( $user ) {
211                 $titles = array();
212                 $dbr = wfGetDB( DB_MASTER );
213                 $uid = intval( $user->getId() );
214                 list( $watchlist, $page ) = $dbr->tableNamesN( 'watchlist', 'page' );
215                 $sql = "SELECT wl_namespace, wl_title, page_id, page_is_redirect
216                         FROM {$watchlist} LEFT JOIN {$page} ON ( wl_namespace = page_namespace
217                         AND wl_title = page_title ) WHERE wl_user = {$uid}";
218                 $res = $dbr->query( $sql, __METHOD__ );
219                 if( $res && $dbr->numRows( $res ) > 0 ) {
220                         $cache = LinkCache::singleton();
221                         while( $row = $dbr->fetchObject( $res ) ) {
222                                 $title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title );
223                                 if( $title instanceof Title ) {
224                                         // Update the link cache while we're at it
225                                         if( $row->page_id ) {
226                                                 $cache->addGoodLinkObj( $row->page_id, $title );
227                                         } else {
228                                                 $cache->addBadLinkObj( $title );
229                                         }
230                                         // Ignore non-talk
231                                         if( !$title->isTalkPage() )
232                                                 $titles[$row->wl_namespace][$row->wl_title] = $row->page_is_redirect;
233                                 }
234                         }
235                 }
236                 return $titles;
237         }
238         
239         /**
240          * Show a message indicating the number of items on the user's watchlist,
241          * and return this count for additional checking
242          *
243          * @param OutputPage $output
244          * @param User $user
245          * @return int
246          */
247         private function showItemCount( $output, $user ) {
248                 if( ( $count = $this->countWatchlist( $user ) ) > 0 ) {
249                         $output->addHtml( wfMsgExt( 'watchlistedit-numitems', 'parse',
250                                 $GLOBALS['wgLang']->formatNum( $count ) ) );
251                 } else {
252                         $output->addHtml( wfMsgExt( 'watchlistedit-noitems', 'parse' ) );
253                 }
254                 return $count;
255         }
256         
257         /**
258          * Remove all titles from a user's watchlist
259          *
260          * @param User $user
261          */
262         private function clearWatchlist( $user ) {
263                 $dbw = wfGetDB( DB_MASTER );
264                 $dbw->delete( 'watchlist', array( 'wl_user' => $user->getId() ), __METHOD__ );
265         }
266
267         /**
268          * Add a list of titles to a user's watchlist
269          *
270          * $titles can be an array of strings or Title objects; the former
271          * is preferred, since Titles are very memory-heavy
272          *
273          * @param array $titles An array of strings, or Title objects
274          * @param User $user
275          */
276         private function watchTitles( $titles, $user ) {
277                 $dbw = wfGetDB( DB_MASTER );
278                 $rows = array();
279                 foreach( $titles as $title ) {
280                         if( !$title instanceof Title )
281                                 $title = Title::newFromText( $title );
282                         if( $title instanceof Title ) {
283                                 $rows[] = array(
284                                         'wl_user' => $user->getId(),
285                                         'wl_namespace' => ( $title->getNamespace() & ~1 ),
286                                         'wl_title' => $title->getDBkey(),
287                                         'wl_notificationtimestamp' => null,
288                                 );
289                                 $rows[] = array(
290                                         'wl_user' => $user->getId(),
291                                         'wl_namespace' => ( $title->getNamespace() | 1 ),
292                                         'wl_title' => $title->getDBkey(),
293                                         'wl_notificationtimestamp' => null,
294                                 );
295                         }
296                 }
297                 $dbw->insert( 'watchlist', $rows, __METHOD__, 'IGNORE' );
298         }
299
300         /**
301          * Remove a list of titles from a user's watchlist
302          *
303          * $titles can be an array of strings or Title objects; the former
304          * is preferred, since Titles are very memory-heavy
305          *
306          * @param array $titles An array of strings, or Title objects
307          * @param User $user
308          */
309         private function unwatchTitles( $titles, $user ) {
310                 $dbw = wfGetDB( DB_MASTER );
311                 foreach( $titles as $title ) {
312                         if( !$title instanceof Title )
313                                 $title = Title::newFromText( $title );
314                         if( $title instanceof Title ) {
315                                 $dbw->delete(
316                                         'watchlist',
317                                         array(
318                                                 'wl_user' => $user->getId(),
319                                                 'wl_namespace' => ( $title->getNamespace() & ~1 ),
320                                                 'wl_title' => $title->getDBkey(),
321                                         ),
322                                         __METHOD__
323                                 );
324                                 $dbw->delete(
325                                         'watchlist',
326                                         array(
327                                                 'wl_user' => $user->getId(),
328                                                 'wl_namespace' => ( $title->getNamespace() | 1 ),
329                                                 'wl_title' => $title->getDBkey(),
330                                         ),
331                                         __METHOD__
332                                 );
333                         }
334                 }
335         }
336
337         /**
338          * Show a confirmation form for users wishing to clear their watchlist
339          *
340          * @param OutputPage $output
341          * @param User $user
342          */
343         private function showClearForm( $output, $user ) {
344                 global $wgUser;
345                 if( ( $count = $this->showItemCount( $output, $user ) ) > 0 ) {
346                         $self = SpecialPage::getTitleFor( 'Watchlist' );
347                         $form  = Xml::openElement( 'form', array( 'method' => 'post',
348                                 'action' => $self->getLocalUrl( 'action=clear' ) ) );
349                         $form .= Xml::hidden( 'token', $wgUser->editToken( 'watchlistedit' ) );
350                         $form .= '<fieldset><legend>' . wfMsgHtml( 'watchlistedit-clear-legend' ) . '</legend>';
351                         $form .= wfMsgExt( 'watchlistedit-clear-confirm', 'parse' );
352                         $form .= '<p>' . Xml::submitButton( wfMsg( 'watchlistedit-clear-submit' ) ) . '</p>';
353                         $form .= '</fieldset></form>';
354                         $output->addHtml( $form );
355                 }
356         }
357         
358         /**
359          * Show the standard watchlist editing form
360          *
361          * @param OutputPage $output
362          * @param User $user
363          */
364         private function showNormalForm( $output, $user ) {
365                 global $wgUser;
366                 if( ( $count = $this->showItemCount( $output, $user ) ) > 0 ) {
367                         $self = SpecialPage::getTitleFor( 'Watchlist' );
368                         $form  = Xml::openElement( 'form', array( 'method' => 'post',
369                                 'action' => $self->getLocalUrl( 'action=edit' ) ) );
370                         $form .= Xml::hidden( 'token', $wgUser->editToken( 'watchlistedit' ) );
371                         $form .= '<fieldset><legend>' . wfMsgHtml( 'watchlistedit-normal-legend' ) . '</legend>';
372                         $form .= wfMsgExt( 'watchlistedit-normal-explain', 'parse' );
373                         foreach( $this->getWatchlistInfo( $user ) as $namespace => $pages ) {
374                                 $form .= '<h2>' . $this->getNamespaceHeading( $namespace ) . '</h2>';
375                                 $form .= '<ul>';
376                                 foreach( $pages as $dbkey => $redirect ) {
377                                         $title = Title::makeTitleSafe( $namespace, $dbkey );
378                                         $form .= $this->buildRemoveLine( $title, $redirect, $wgUser->getSkin() );
379                                 }
380                                 $form .= '</ul>';
381                         }
382                         $form .= '<p>' . Xml::submitButton( wfMsg( 'watchlistedit-normal-submit' ) ) . '</p>';
383                         $form .= '</fieldset></form>';
384                         $output->addHtml( $form );
385                 }
386         }
387         
388         /**
389          * Get the correct "heading" for a namespace
390          *
391          * @param int $namespace
392          * @return string
393          */
394         private function getNamespaceHeading( $namespace ) {
395                 return $namespace == NS_MAIN
396                         ? wfMsgHtml( 'blanknamespace' )
397                         : htmlspecialchars( $GLOBALS['wgContLang']->getFormattedNsText( $namespace ) );
398         }
399         
400         /**
401          * Build a single list item containing a check box selecting a title
402          * and a link to that title, with various additional bits
403          *
404          * @param Title $title
405          * @param bool $redirect
406          * @param Skin $skin
407          * @return string
408          */
409         private function buildRemoveLine( $title, $redirect, $skin ) {
410                 $link = $skin->makeLinkObj( $title );
411                 if( $redirect )
412                         $link = '<span class="watchlistredir">' . $link . '</span>';
413                 $tools[] = $skin->makeLinkObj( $title->getTalkPage(), wfMsgHtml( 'talkpagelinktext' ) );
414                 if( $title->exists() ) {
415                         $tools[] = $skin->makeKnownLinkObj( $title, wfMsgHtml( 'history_short' ), 'action=history' );
416                 }
417                 if( $title->getNamespace() == NS_USER && !$title->isSubpage() ) {
418                         $tools[] = $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Contributions', $title->getText() ), wfMsgHtml( 'contributions' ) );
419                 }
420                 return '<li>'
421                         . Xml::check( 'titles[]', false, array( 'value' => $title->getPrefixedText() ) )
422                         . $link . ' (' . implode( ' | ', $tools ) . ')' . '</li>';
423                 }
424         
425         /**
426          * Show a form for editing the watchlist in "raw" mode
427          *
428          * @param OutputPage $output
429          * @param User $user
430          */
431         public function showRawForm( $output, $user ) {
432                 global $wgUser;
433                 $this->showItemCount( $output, $user );
434                 $self = SpecialPage::getTitleFor( 'Watchlist' );
435                 $form  = Xml::openElement( 'form', array( 'method' => 'post',
436                         'action' => $self->getLocalUrl( 'action=raw' ) ) );
437                 $form .= Xml::hidden( 'token', $wgUser->editToken( 'watchlistedit' ) );
438                 $form .= '<fieldset><legend>' . wfMsgHtml( 'watchlistedit-raw-legend' ) . '</legend>';
439                 $form .= wfMsgExt( 'watchlistedit-raw-explain', 'parse' );
440                 $form .= Xml::label( wfMsg( 'watchlistedit-raw-titles' ), 'titles' );
441                 $form .= "<br />\n";
442                 $form .= Xml::openElement( 'textarea', array( 'id' => 'titles', 'name' => 'titles',
443                         'rows' => $wgUser->getIntOption( 'rows' ), 'cols' => $wgUser->getIntOption( 'cols' ) ) );
444                 $titles = $this->getWatchlist( $user );
445                 foreach( $titles as $title )
446                         $form .= htmlspecialchars( $title ) . "\n";
447                 $form .= '</textarea>';
448                 $form .= '<p>' . Xml::submitButton( wfMsg( 'watchlistedit-raw-submit' ) ) . '</p>';
449                 $form .= '</fieldset></form>';
450                 $output->addHtml( $form );
451         }
452         
453         /**
454          * Determine whether we are editing the watchlist, and if so, what
455          * kind of editing operation
456          *
457          * @param WebRequest $request
458          * @param mixed $par
459          * @return int
460          */
461         public static function getMode( $request, $par ) {
462                 $mode = strtolower( $request->getVal( 'action', $par ) );
463                 switch( $mode ) {
464                         case 'clear':
465                                 return self::EDIT_CLEAR;
466                         case 'raw':
467                                 return self::EDIT_RAW;
468                         case 'edit':
469                                 return self::EDIT_NORMAL;
470                         default:
471                                 return false;
472                 }
473         }
474         
475         /**
476          * Build a set of links for convenient navigation
477          * between watchlist viewing and editing modes
478          *
479          * @param Skin $skin Skin to use
480          * @return string
481          */
482         public static function buildTools( $skin ) {
483                 $tools = array();
484                 $self = SpecialPage::getTitleFor( 'Watchlist' );
485                 $modes = array( 'view' => '', 'edit' => 'edit', 'raw' => 'raw', 'clear' => 'clear' );
486                 foreach( $modes as $mode => $action ) {
487                         $action = $action ? "action={$action}" : '';
488                         $tools[] = $skin->makeKnownLinkObj( $self, wfMsgHtml( "watchlisttools-{$mode}" ), $action );
489                 }
490                 return implode( ' | ', $tools );
491         }
492
493 }