Index: trunk/phase3/includes/WatchlistEditor.php |
— | — | @@ -1,542 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -/** |
5 | | - * Provides the UI through which users can perform editing |
6 | | - * operations on their watchlist |
7 | | - * |
8 | | - * @ingroup Watchlist |
9 | | - * @author Rob Church <robchur@gmail.com> |
10 | | - */ |
11 | | -class WatchlistEditor { |
12 | | - |
13 | | - /** |
14 | | - * Editing modes |
15 | | - */ |
16 | | - const EDIT_CLEAR = 1; |
17 | | - const EDIT_RAW = 2; |
18 | | - const EDIT_NORMAL = 3; |
19 | | - |
20 | | - /** |
21 | | - * Main execution point |
22 | | - * |
23 | | - * @param $user User |
24 | | - * @param $output OutputPage |
25 | | - * @param $request WebRequest |
26 | | - * @param $mode int |
27 | | - */ |
28 | | - public function execute( $user, $output, $request, $mode ) { |
29 | | - global $wgUser, $wgLang; |
30 | | - if( wfReadOnly() ) { |
31 | | - $output->readOnlyPage(); |
32 | | - return; |
33 | | - } |
34 | | - switch( $mode ) { |
35 | | - case self::EDIT_CLEAR: |
36 | | - // The "Clear" link scared people too much. |
37 | | - // Pass on to the raw editor, from which it's very easy to clear. |
38 | | - case self::EDIT_RAW: |
39 | | - $output->setPageTitle( wfMsg( 'watchlistedit-raw-title' ) ); |
40 | | - if( $request->wasPosted() && $this->checkToken( $request, $wgUser ) ) { |
41 | | - $wanted = $this->extractTitles( $request->getText( 'titles' ) ); |
42 | | - $current = $this->getWatchlist( $user ); |
43 | | - if( count( $wanted ) > 0 ) { |
44 | | - $toWatch = array_diff( $wanted, $current ); |
45 | | - $toUnwatch = array_diff( $current, $wanted ); |
46 | | - $this->watchTitles( $toWatch, $user ); |
47 | | - $this->unwatchTitles( $toUnwatch, $user ); |
48 | | - $user->invalidateCache(); |
49 | | - if( count( $toWatch ) > 0 || count( $toUnwatch ) > 0 ) |
50 | | - $output->addHTML( wfMsgExt( 'watchlistedit-raw-done', 'parse' ) ); |
51 | | - if( ( $count = count( $toWatch ) ) > 0 ) { |
52 | | - $output->addHTML( wfMsgExt( 'watchlistedit-raw-added', 'parse', |
53 | | - $wgLang->formatNum( $count ) ) ); |
54 | | - $this->showTitles( $toWatch, $output, $wgUser->getSkin() ); |
55 | | - } |
56 | | - if( ( $count = count( $toUnwatch ) ) > 0 ) { |
57 | | - $output->addHTML( wfMsgExt( 'watchlistedit-raw-removed', 'parse', |
58 | | - $wgLang->formatNum( $count ) ) ); |
59 | | - $this->showTitles( $toUnwatch, $output, $wgUser->getSkin() ); |
60 | | - } |
61 | | - } else { |
62 | | - $this->clearWatchlist( $user ); |
63 | | - $user->invalidateCache(); |
64 | | - $output->addHTML( wfMsgExt( 'watchlistedit-raw-removed', 'parse', |
65 | | - $wgLang->formatNum( count( $current ) ) ) ); |
66 | | - $this->showTitles( $current, $output, $wgUser->getSkin() ); |
67 | | - } |
68 | | - } |
69 | | - $this->showRawForm( $output, $user ); |
70 | | - break; |
71 | | - case self::EDIT_NORMAL: |
72 | | - $output->setPageTitle( wfMsg( 'watchlistedit-normal-title' ) ); |
73 | | - if( $request->wasPosted() && $this->checkToken( $request, $wgUser ) ) { |
74 | | - $titles = $this->extractTitles( $request->getArray( 'titles' ) ); |
75 | | - $this->unwatchTitles( $titles, $user ); |
76 | | - $user->invalidateCache(); |
77 | | - $output->addHTML( wfMsgExt( 'watchlistedit-normal-done', 'parse', |
78 | | - $wgLang->formatNum( count( $titles ) ) ) ); |
79 | | - $this->showTitles( $titles, $output, $wgUser->getSkin() ); |
80 | | - } |
81 | | - $this->showNormalForm( $output, $user ); |
82 | | - } |
83 | | - } |
84 | | - |
85 | | - /** |
86 | | - * Check the edit token from a form submission |
87 | | - * |
88 | | - * @param $request WebRequest |
89 | | - * @param $user User |
90 | | - * @return bool |
91 | | - */ |
92 | | - private function checkToken( $request, $user ) { |
93 | | - return $user->matchEditToken( $request->getVal( 'token' ), 'watchlistedit' ); |
94 | | - } |
95 | | - |
96 | | - /** |
97 | | - * Extract a list of titles from a blob of text, returning |
98 | | - * (prefixed) strings; unwatchable titles are ignored |
99 | | - * |
100 | | - * @param $list mixed |
101 | | - * @return array |
102 | | - */ |
103 | | - private function extractTitles( $list ) { |
104 | | - $titles = array(); |
105 | | - if( !is_array( $list ) ) { |
106 | | - $list = explode( "\n", trim( $list ) ); |
107 | | - if( !is_array( $list ) ) { |
108 | | - return array(); |
109 | | - } |
110 | | - } |
111 | | - foreach( $list as $text ) { |
112 | | - $text = trim( $text ); |
113 | | - if( strlen( $text ) > 0 ) { |
114 | | - $title = Title::newFromText( $text ); |
115 | | - if( $title instanceof Title && $title->isWatchable() ) { |
116 | | - $titles[] = $title->getPrefixedText(); |
117 | | - } |
118 | | - } |
119 | | - } |
120 | | - return array_unique( $titles ); |
121 | | - } |
122 | | - |
123 | | - /** |
124 | | - * Print out a list of linked titles |
125 | | - * |
126 | | - * $titles can be an array of strings or Title objects; the former |
127 | | - * is preferred, since Titles are very memory-heavy |
128 | | - * |
129 | | - * @param $titles An array of strings, or Title objects |
130 | | - * @param $output OutputPage |
131 | | - * @param $skin Skin |
132 | | - */ |
133 | | - private function showTitles( $titles, $output, $skin ) { |
134 | | - $talk = wfMsgHtml( 'talkpagelinktext' ); |
135 | | - // Do a batch existence check |
136 | | - $batch = new LinkBatch(); |
137 | | - foreach( $titles as $title ) { |
138 | | - if( !$title instanceof Title ) { |
139 | | - $title = Title::newFromText( $title ); |
140 | | - } |
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 | | - } |
153 | | - if( $title instanceof Title ) { |
154 | | - $output->addHTML( "<li>" . $skin->link( $title ) |
155 | | - . ' (' . $skin->link( $title->getTalkPage(), $talk ) . ")</li>\n" ); |
156 | | - } |
157 | | - } |
158 | | - $output->addHTML( "</ul>\n" ); |
159 | | - } |
160 | | - |
161 | | - /** |
162 | | - * Count the number of titles on a user's watchlist, excluding talk pages |
163 | | - * |
164 | | - * @param $user User |
165 | | - * @return int |
166 | | - */ |
167 | | - private function countWatchlist( $user ) { |
168 | | - $dbr = wfGetDB( DB_MASTER ); |
169 | | - $res = $dbr->select( 'watchlist', 'COUNT(*) AS count', array( 'wl_user' => $user->getId() ), __METHOD__ ); |
170 | | - $row = $dbr->fetchObject( $res ); |
171 | | - return ceil( $row->count / 2 ); // Paranoia |
172 | | - } |
173 | | - |
174 | | - /** |
175 | | - * Prepare a list of titles on a user's watchlist (excluding talk pages) |
176 | | - * and return an array of (prefixed) strings |
177 | | - * |
178 | | - * @param $user User |
179 | | - * @return array |
180 | | - */ |
181 | | - private function getWatchlist( $user ) { |
182 | | - $list = array(); |
183 | | - $dbr = wfGetDB( DB_MASTER ); |
184 | | - $res = $dbr->select( |
185 | | - 'watchlist', |
186 | | - '*', |
187 | | - array( |
188 | | - 'wl_user' => $user->getId(), |
189 | | - ), |
190 | | - __METHOD__ |
191 | | - ); |
192 | | - if( $res->numRows() > 0 ) { |
193 | | - foreach ( $res as $row ) { |
194 | | - $title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title ); |
195 | | - if( $title instanceof Title && !$title->isTalkPage() ) |
196 | | - $list[] = $title->getPrefixedText(); |
197 | | - } |
198 | | - $res->free(); |
199 | | - } |
200 | | - return $list; |
201 | | - } |
202 | | - |
203 | | - /** |
204 | | - * Get a list of titles on a user's watchlist, excluding talk pages, |
205 | | - * and return as a two-dimensional array with namespace, title and |
206 | | - * redirect status |
207 | | - * |
208 | | - * @param $user User |
209 | | - * @return array |
210 | | - */ |
211 | | - private function getWatchlistInfo( $user ) { |
212 | | - $titles = array(); |
213 | | - $dbr = wfGetDB( DB_MASTER ); |
214 | | - |
215 | | - $res = $dbr->select( |
216 | | - array( 'watchlist', 'page' ), |
217 | | - array( |
218 | | - 'wl_namespace', |
219 | | - 'wl_title', |
220 | | - 'page_id', |
221 | | - 'page_len', |
222 | | - 'page_is_redirect', |
223 | | - 'page_latest' |
224 | | - ), |
225 | | - array( 'wl_user' => $user->getId() ), |
226 | | - __METHOD__, |
227 | | - array( 'ORDER BY' => 'wl_namespace, wl_title' ), |
228 | | - array( 'page' => array( |
229 | | - 'LEFT JOIN', |
230 | | - 'wl_namespace = page_namespace AND wl_title = page_title' |
231 | | - ) ) |
232 | | - ); |
233 | | - |
234 | | - if( $res && $dbr->numRows( $res ) > 0 ) { |
235 | | - $cache = LinkCache::singleton(); |
236 | | - foreach ( $res as $row ) { |
237 | | - $title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title ); |
238 | | - if( $title instanceof Title ) { |
239 | | - // Update the link cache while we're at it |
240 | | - if( $row->page_id ) { |
241 | | - $cache->addGoodLinkObj( $row->page_id, $title, $row->page_len, $row->page_is_redirect, $row->page_latest ); |
242 | | - } else { |
243 | | - $cache->addBadLinkObj( $title ); |
244 | | - } |
245 | | - // Ignore non-talk |
246 | | - if( !$title->isTalkPage() ) { |
247 | | - $titles[$row->wl_namespace][$row->wl_title] = $row->page_is_redirect; |
248 | | - } |
249 | | - } |
250 | | - } |
251 | | - } |
252 | | - return $titles; |
253 | | - } |
254 | | - |
255 | | - /** |
256 | | - * Show a message indicating the number of items on the user's watchlist, |
257 | | - * and return this count for additional checking |
258 | | - * |
259 | | - * @param $output OutputPage |
260 | | - * @param $user User |
261 | | - * @return int |
262 | | - */ |
263 | | - private function showItemCount( $output, $user ) { |
264 | | - if( ( $count = $this->countWatchlist( $user ) ) > 0 ) { |
265 | | - $output->addHTML( wfMsgExt( 'watchlistedit-numitems', 'parse', |
266 | | - $GLOBALS['wgLang']->formatNum( $count ) ) ); |
267 | | - } else { |
268 | | - $output->addHTML( wfMsgExt( 'watchlistedit-noitems', 'parse' ) ); |
269 | | - } |
270 | | - return $count; |
271 | | - } |
272 | | - |
273 | | - /** |
274 | | - * Remove all titles from a user's watchlist |
275 | | - * |
276 | | - * @param $user User |
277 | | - */ |
278 | | - private function clearWatchlist( $user ) { |
279 | | - $dbw = wfGetDB( DB_MASTER ); |
280 | | - $dbw->delete( 'watchlist', array( 'wl_user' => $user->getId() ), __METHOD__ ); |
281 | | - } |
282 | | - |
283 | | - /** |
284 | | - * Add a list of titles to a user's watchlist |
285 | | - * |
286 | | - * $titles can be an array of strings or Title objects; the former |
287 | | - * is preferred, since Titles are very memory-heavy |
288 | | - * |
289 | | - * @param $titles An array of strings, or Title objects |
290 | | - * @param $user User |
291 | | - */ |
292 | | - private function watchTitles( $titles, $user ) { |
293 | | - $dbw = wfGetDB( DB_MASTER ); |
294 | | - $rows = array(); |
295 | | - foreach( $titles as $title ) { |
296 | | - if( !$title instanceof Title ) { |
297 | | - $title = Title::newFromText( $title ); |
298 | | - } |
299 | | - if( $title instanceof Title ) { |
300 | | - $rows[] = array( |
301 | | - 'wl_user' => $user->getId(), |
302 | | - 'wl_namespace' => ( $title->getNamespace() & ~1 ), |
303 | | - 'wl_title' => $title->getDBkey(), |
304 | | - 'wl_notificationtimestamp' => null, |
305 | | - ); |
306 | | - $rows[] = array( |
307 | | - 'wl_user' => $user->getId(), |
308 | | - 'wl_namespace' => ( $title->getNamespace() | 1 ), |
309 | | - 'wl_title' => $title->getDBkey(), |
310 | | - 'wl_notificationtimestamp' => null, |
311 | | - ); |
312 | | - } |
313 | | - } |
314 | | - $dbw->insert( 'watchlist', $rows, __METHOD__, 'IGNORE' ); |
315 | | - } |
316 | | - |
317 | | - /** |
318 | | - * Remove a list of titles from a user's watchlist |
319 | | - * |
320 | | - * $titles can be an array of strings or Title objects; the former |
321 | | - * is preferred, since Titles are very memory-heavy |
322 | | - * |
323 | | - * @param $titles An array of strings, or Title objects |
324 | | - * @param $user User |
325 | | - */ |
326 | | - private function unwatchTitles( $titles, $user ) { |
327 | | - $dbw = wfGetDB( DB_MASTER ); |
328 | | - foreach( $titles as $title ) { |
329 | | - if( !$title instanceof Title ) { |
330 | | - $title = Title::newFromText( $title ); |
331 | | - } |
332 | | - if( $title instanceof Title ) { |
333 | | - $dbw->delete( |
334 | | - 'watchlist', |
335 | | - array( |
336 | | - 'wl_user' => $user->getId(), |
337 | | - 'wl_namespace' => ( $title->getNamespace() & ~1 ), |
338 | | - 'wl_title' => $title->getDBkey(), |
339 | | - ), |
340 | | - __METHOD__ |
341 | | - ); |
342 | | - $dbw->delete( |
343 | | - 'watchlist', |
344 | | - array( |
345 | | - 'wl_user' => $user->getId(), |
346 | | - 'wl_namespace' => ( $title->getNamespace() | 1 ), |
347 | | - 'wl_title' => $title->getDBkey(), |
348 | | - ), |
349 | | - __METHOD__ |
350 | | - ); |
351 | | - $article = new Article($title); |
352 | | - wfRunHooks('UnwatchArticleComplete',array(&$user,&$article)); |
353 | | - } |
354 | | - } |
355 | | - } |
356 | | - |
357 | | - /** |
358 | | - * Show the standard watchlist editing form |
359 | | - * |
360 | | - * @param $output OutputPage |
361 | | - * @param $user User |
362 | | - */ |
363 | | - private function showNormalForm( $output, $user ) { |
364 | | - global $wgUser; |
365 | | - $count = $this->showItemCount( $output, $user ); |
366 | | - if( $count > 0 ) { |
367 | | - $self = SpecialPage::getTitleFor( 'Watchlist' ); |
368 | | - $form = Xml::openElement( 'form', array( 'method' => 'post', |
369 | | - 'action' => $self->getLocalUrl( array( 'action' => 'edit' ) ) ) ); |
370 | | - $form .= Html::hidden( 'token', $wgUser->editToken( 'watchlistedit' ) ); |
371 | | - $form .= "<fieldset>\n<legend>" . wfMsgHtml( 'watchlistedit-normal-legend' ) . "</legend>"; |
372 | | - $form .= wfMsgExt( 'watchlistedit-normal-explain', 'parse' ); |
373 | | - $form .= $this->buildRemoveList( $user, $wgUser->getSkin() ); |
374 | | - $form .= '<p>' . Xml::submitButton( wfMsg( 'watchlistedit-normal-submit' ) ) . '</p>'; |
375 | | - $form .= '</fieldset></form>'; |
376 | | - $output->addHTML( $form ); |
377 | | - } |
378 | | - } |
379 | | - |
380 | | - /** |
381 | | - * Build the part of the standard watchlist editing form with the actual |
382 | | - * title selection checkboxes and stuff. Also generates a table of |
383 | | - * contents if there's more than one heading. |
384 | | - * |
385 | | - * @param $user User |
386 | | - * @param $skin Skin (really, Linker) |
387 | | - */ |
388 | | - private function buildRemoveList( $user, $skin ) { |
389 | | - $list = ""; |
390 | | - $toc = $skin->tocIndent(); |
391 | | - $tocLength = 0; |
392 | | - foreach( $this->getWatchlistInfo( $user ) as $namespace => $pages ) { |
393 | | - $tocLength++; |
394 | | - $heading = htmlspecialchars( $this->getNamespaceHeading( $namespace ) ); |
395 | | - $anchor = "editwatchlist-ns" . $namespace; |
396 | | - |
397 | | - $list .= $skin->makeHeadLine( 2, ">", $anchor, $heading, "" ); |
398 | | - $toc .= $skin->tocLine( $anchor, $heading, $tocLength, 1 ) . $skin->tocLineEnd(); |
399 | | - |
400 | | - $list .= "<ul>\n"; |
401 | | - foreach( $pages as $dbkey => $redirect ) { |
402 | | - $title = Title::makeTitleSafe( $namespace, $dbkey ); |
403 | | - $list .= $this->buildRemoveLine( $title, $redirect, $skin ); |
404 | | - } |
405 | | - $list .= "</ul>\n"; |
406 | | - } |
407 | | - // ISSUE: omit the TOC if the total number of titles is low? |
408 | | - if( $tocLength > 1 ) { |
409 | | - $list = $skin->tocList( $toc ) . $list; |
410 | | - } |
411 | | - return $list; |
412 | | - } |
413 | | - |
414 | | - /** |
415 | | - * Get the correct "heading" for a namespace |
416 | | - * |
417 | | - * @param $namespace int |
418 | | - * @return string |
419 | | - */ |
420 | | - private function getNamespaceHeading( $namespace ) { |
421 | | - return $namespace == NS_MAIN |
422 | | - ? wfMsgHtml( 'blanknamespace' ) |
423 | | - : htmlspecialchars( $GLOBALS['wgContLang']->getFormattedNsText( $namespace ) ); |
424 | | - } |
425 | | - |
426 | | - /** |
427 | | - * Build a single list item containing a check box selecting a title |
428 | | - * and a link to that title, with various additional bits |
429 | | - * |
430 | | - * @param $title Title |
431 | | - * @param $redirect bool |
432 | | - * @param $skin Skin |
433 | | - * @return string |
434 | | - */ |
435 | | - private function buildRemoveLine( $title, $redirect, $skin ) { |
436 | | - global $wgLang; |
437 | | - |
438 | | - $link = $skin->link( $title ); |
439 | | - if( $redirect ) { |
440 | | - $link = '<span class="watchlistredir">' . $link . '</span>'; |
441 | | - } |
442 | | - $tools[] = $skin->link( $title->getTalkPage(), wfMsgHtml( 'talkpagelinktext' ) ); |
443 | | - if( $title->exists() ) { |
444 | | - $tools[] = $skin->link( |
445 | | - $title, |
446 | | - wfMsgHtml( 'history_short' ), |
447 | | - array(), |
448 | | - array( 'action' => 'history' ), |
449 | | - array( 'known', 'noclasses' ) |
450 | | - ); |
451 | | - } |
452 | | - if( $title->getNamespace() == NS_USER && !$title->isSubpage() ) { |
453 | | - $tools[] = $skin->link( |
454 | | - SpecialPage::getTitleFor( 'Contributions', $title->getText() ), |
455 | | - wfMsgHtml( 'contributions' ), |
456 | | - array(), |
457 | | - array(), |
458 | | - array( 'known', 'noclasses' ) |
459 | | - ); |
460 | | - } |
461 | | - |
462 | | - wfRunHooks( 'WatchlistEditorBuildRemoveLine', array( &$tools, $title, $redirect, $skin ) ); |
463 | | - |
464 | | - return "<li>" |
465 | | - . Xml::check( 'titles[]', false, array( 'value' => $title->getPrefixedText() ) ) |
466 | | - . $link . " (" . $wgLang->pipeList( $tools ) . ")" . "</li>\n"; |
467 | | - } |
468 | | - |
469 | | - /** |
470 | | - * Show a form for editing the watchlist in "raw" mode |
471 | | - * |
472 | | - * @param $output OutputPage |
473 | | - * @param $user User |
474 | | - */ |
475 | | - public function showRawForm( $output, $user ) { |
476 | | - global $wgUser; |
477 | | - $this->showItemCount( $output, $user ); |
478 | | - $self = SpecialPage::getTitleFor( 'Watchlist' ); |
479 | | - $form = Xml::openElement( 'form', array( 'method' => 'post', |
480 | | - 'action' => $self->getLocalUrl( array( 'action' => 'raw' ) ) ) ); |
481 | | - $form .= Html::hidden( 'token', $wgUser->editToken( 'watchlistedit' ) ); |
482 | | - $form .= '<fieldset><legend>' . wfMsgHtml( 'watchlistedit-raw-legend' ) . '</legend>'; |
483 | | - $form .= wfMsgExt( 'watchlistedit-raw-explain', 'parse' ); |
484 | | - $form .= Xml::label( wfMsg( 'watchlistedit-raw-titles' ), 'titles' ); |
485 | | - $form .= "<br />\n"; |
486 | | - $form .= Xml::openElement( 'textarea', array( 'id' => 'titles', 'name' => 'titles', |
487 | | - 'rows' => $wgUser->getIntOption( 'rows' ), 'cols' => $wgUser->getIntOption( 'cols' ) ) ); |
488 | | - $titles = $this->getWatchlist( $user ); |
489 | | - foreach( $titles as $title ) { |
490 | | - $form .= htmlspecialchars( $title ) . "\n"; |
491 | | - } |
492 | | - $form .= '</textarea>'; |
493 | | - $form .= '<p>' . Xml::submitButton( wfMsg( 'watchlistedit-raw-submit' ) ) . '</p>'; |
494 | | - $form .= '</fieldset></form>'; |
495 | | - $output->addHTML( $form ); |
496 | | - } |
497 | | - |
498 | | - /** |
499 | | - * Determine whether we are editing the watchlist, and if so, what |
500 | | - * kind of editing operation |
501 | | - * |
502 | | - * @param $request WebRequest |
503 | | - * @param $par mixed |
504 | | - * @return int |
505 | | - */ |
506 | | - public static function getMode( $request, $par ) { |
507 | | - $mode = strtolower( $request->getVal( 'action', $par ) ); |
508 | | - switch( $mode ) { |
509 | | - case 'clear': |
510 | | - return self::EDIT_CLEAR; |
511 | | - case 'raw': |
512 | | - return self::EDIT_RAW; |
513 | | - case 'edit': |
514 | | - return self::EDIT_NORMAL; |
515 | | - default: |
516 | | - return false; |
517 | | - } |
518 | | - } |
519 | | - |
520 | | - /** |
521 | | - * Build a set of links for convenient navigation |
522 | | - * between watchlist viewing and editing modes |
523 | | - * |
524 | | - * @param $skin Skin to use |
525 | | - * @return string |
526 | | - */ |
527 | | - public static function buildTools( $skin ) { |
528 | | - global $wgLang; |
529 | | - |
530 | | - $tools = array(); |
531 | | - $modes = array( 'view' => false, 'edit' => 'edit', 'raw' => 'raw' ); |
532 | | - foreach( $modes as $mode => $subpage ) { |
533 | | - // can use messages 'watchlisttools-view', 'watchlisttools-edit', 'watchlisttools-raw' |
534 | | - $tools[] = $skin->linkKnown( |
535 | | - SpecialPage::getTitleFor( 'Watchlist', $subpage ), |
536 | | - wfMsgHtml( "watchlisttools-{$mode}" ) |
537 | | - ); |
538 | | - } |
539 | | - return Html::rawElement( 'span', |
540 | | - array( 'class' => 'mw-watchlist-toollinks' ), |
541 | | - wfMsg( 'parentheses', $wgLang->pipeList( $tools ) ) ); |
542 | | - } |
543 | | -} |
Index: trunk/phase3/includes/AutoLoader.php |
— | — | @@ -250,7 +250,7 @@ |
251 | 251 | 'ViewCountUpdate' => 'includes/ViewCountUpdate.php', |
252 | 252 | 'WantedQueryPage' => 'includes/QueryPage.php', |
253 | 253 | 'WatchedItem' => 'includes/WatchedItem.php', |
254 | | - 'WatchlistEditor' => 'includes/WatchlistEditor.php', |
| 254 | + 'WatchlistEditor' => 'includes/specials/SpecialEditWatchlist.php', |
255 | 255 | 'WebRequest' => 'includes/WebRequest.php', |
256 | 256 | 'WebRequestUpload' => 'includes/WebRequest.php', |
257 | 257 | 'WebResponse' => 'includes/WebResponse.php', |
— | — | @@ -655,6 +655,7 @@ |
656 | 656 | 'SpecialCategories' => 'includes/specials/SpecialCategories.php', |
657 | 657 | 'SpecialComparePages' => 'includes/specials/SpecialComparePages.php', |
658 | 658 | 'SpecialDisableAccount' => 'includes/specials/SpecialDisableAccount.php', |
| 659 | + 'SpecialEditWatchlist' => 'includes/specials/SpecialEditWatchlist.php', |
659 | 660 | 'SpecialExport' => 'includes/specials/SpecialExport.php', |
660 | 661 | 'SpecialFilepath' => 'includes/specials/SpecialFilepath.php', |
661 | 662 | 'SpecialImport' => 'includes/specials/SpecialImport.php', |
Index: trunk/phase3/includes/specials/SpecialEditWatchlist.php |
— | — | @@ -0,0 +1,603 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Provides the UI through which users can perform editing |
| 6 | + * operations on their watchlist |
| 7 | + * |
| 8 | + * @ingroup Watchlist |
| 9 | + * @author Rob Church <robchur@gmail.com> |
| 10 | + */ |
| 11 | +class SpecialEditWatchlist extends UnlistedSpecialPage { |
| 12 | + |
| 13 | + /** |
| 14 | + * Editing modes |
| 15 | + */ |
| 16 | + const EDIT_CLEAR = 1; |
| 17 | + const EDIT_RAW = 2; |
| 18 | + const EDIT_NORMAL = 3; |
| 19 | + |
| 20 | + protected $successMessage; |
| 21 | + |
| 22 | + public function __construct(){ |
| 23 | + parent::__construct( 'EditWatchlist' ); |
| 24 | + } |
| 25 | + |
| 26 | + /** |
| 27 | + * Main execution point |
| 28 | + * |
| 29 | + * @param $user User |
| 30 | + * @param $output OutputPage |
| 31 | + * @param $request WebRequest |
| 32 | + * @param $mode int |
| 33 | + */ |
| 34 | + public function execute( $mode ) { |
| 35 | + global $wgUser, $wgLang, $wgOut, $wgRequest; |
| 36 | + if( wfReadOnly() ) { |
| 37 | + $wgOut->readOnlyPage(); |
| 38 | + return; |
| 39 | + } |
| 40 | + |
| 41 | + # Anons don't get a watchlist |
| 42 | + if( $wgUser->isAnon() ) { |
| 43 | + $wgOut->setPageTitle( wfMsg( 'watchnologin' ) ); |
| 44 | + $llink = $wgUser->getSkin()->linkKnown( |
| 45 | + SpecialPage::getTitleFor( 'Userlogin' ), |
| 46 | + wfMsgHtml( 'loginreqlink' ), |
| 47 | + array(), |
| 48 | + array( 'returnto' => $this->getTitle()->getPrefixedText() ) |
| 49 | + ); |
| 50 | + $wgOut->addWikiMsgArray( 'watchlistanontext', array( $llink ), array( 'replaceafter' ) ); |
| 51 | + return; |
| 52 | + } |
| 53 | + |
| 54 | + $sub = wfMsgExt( |
| 55 | + 'watchlistfor2', |
| 56 | + array( 'parseinline', 'replaceafter' ), |
| 57 | + $wgUser->getName(), |
| 58 | + SpecialEditWatchlist::buildTools( $wgUser->getSkin() ) |
| 59 | + ); |
| 60 | + $wgOut->setSubtitle( $sub ); |
| 61 | + |
| 62 | + # B/C: $mode used to be waaay down the parameter list, and the first parameter |
| 63 | + # was $wgUser |
| 64 | + if( $mode instanceof User ){ |
| 65 | + $args = func_get_args(); |
| 66 | + if( count( $args >= 4 ) ){ |
| 67 | + $mode = $args[3]; |
| 68 | + } |
| 69 | + } |
| 70 | + $mode = self::getMode( $wgRequest, $mode ); |
| 71 | + |
| 72 | + switch( $mode ) { |
| 73 | + case self::EDIT_CLEAR: |
| 74 | + // The "Clear" link scared people too much. |
| 75 | + // Pass on to the raw editor, from which it's very easy to clear. |
| 76 | + |
| 77 | + case self::EDIT_RAW: |
| 78 | + $wgOut->setPageTitle( wfMsg( 'watchlistedit-raw-title' ) ); |
| 79 | + $form = $this->getRawForm( $wgUser ); |
| 80 | + if( $form->show() ){ |
| 81 | + $wgOut->addHTML( $this->successMessage ); |
| 82 | + $wgOut->returnToMain(); |
| 83 | + } |
| 84 | + break; |
| 85 | + |
| 86 | + case self::EDIT_NORMAL: |
| 87 | + default: |
| 88 | + $wgOut->setPageTitle( wfMsg( 'watchlistedit-normal-title' ) ); |
| 89 | + $form = $this->getNormalForm( $wgUser ); |
| 90 | + if( $form->show() ){ |
| 91 | + $wgOut->addHTML( $this->successMessage ); |
| 92 | + $wgOut->returnToMain(); |
| 93 | + } |
| 94 | + break; |
| 95 | + } |
| 96 | + } |
| 97 | + |
| 98 | + /** |
| 99 | + * Extract a list of titles from a blob of text, returning |
| 100 | + * (prefixed) strings; unwatchable titles are ignored |
| 101 | + * |
| 102 | + * @param $list String |
| 103 | + * @return array |
| 104 | + */ |
| 105 | + private function extractTitles( $list ) { |
| 106 | + $titles = array(); |
| 107 | + $list = explode( "\n", trim( $list ) ); |
| 108 | + if( !is_array( $list ) ) { |
| 109 | + return array(); |
| 110 | + } |
| 111 | + foreach( $list as $text ) { |
| 112 | + $text = trim( $text ); |
| 113 | + if( strlen( $text ) > 0 ) { |
| 114 | + $title = Title::newFromText( $text ); |
| 115 | + if( $title instanceof Title && $title->isWatchable() ) { |
| 116 | + $titles[] = $title->getPrefixedText(); |
| 117 | + } |
| 118 | + } |
| 119 | + } |
| 120 | + return array_unique( $titles ); |
| 121 | + } |
| 122 | + |
| 123 | + public function submitRaw( $data ){ |
| 124 | + global $wgUser, $wgLang; |
| 125 | + $wanted = $this->extractTitles( $data['Titles'] ); |
| 126 | + $current = $this->getWatchlist( $wgUser ); |
| 127 | + |
| 128 | + if( count( $wanted ) > 0 ) { |
| 129 | + $toWatch = array_diff( $wanted, $current ); |
| 130 | + $toUnwatch = array_diff( $current, $wanted ); |
| 131 | + $this->watchTitles( $toWatch, $wgUser ); |
| 132 | + $this->unwatchTitles( $toUnwatch, $wgUser ); |
| 133 | + $wgUser->invalidateCache(); |
| 134 | + |
| 135 | + if( count( $toWatch ) > 0 || count( $toUnwatch ) > 0 ){ |
| 136 | + $this->successMessage = wfMessage( 'watchlistedit-raw-done' )->parse(); |
| 137 | + } else { |
| 138 | + return false; |
| 139 | + } |
| 140 | + |
| 141 | + if( count( $toWatch ) > 0 ) { |
| 142 | + $this->successMessage .= wfMessage( |
| 143 | + 'watchlistedit-raw-added', |
| 144 | + $wgLang->formatNum( count( $toWatch ) ) |
| 145 | + ); |
| 146 | + $this->showTitles( $toWatch, $this->successMessage, $wgUser->getSkin() ); |
| 147 | + } |
| 148 | + |
| 149 | + if( count( $toUnwatch ) > 0 ) { |
| 150 | + $this->successMessage .= wfMessage( |
| 151 | + 'watchlistedit-raw-removed', |
| 152 | + $wgLang->formatNum( count( $toUnwatch ) ) |
| 153 | + ); |
| 154 | + $this->showTitles( $toUnwatch, $this->successMessage, $wgUser->getSkin() ); |
| 155 | + } |
| 156 | + } else { |
| 157 | + $this->clearWatchlist( $wgUser ); |
| 158 | + $wgUser->invalidateCache(); |
| 159 | + $this->successMessage .= wfMessage( |
| 160 | + 'watchlistedit-raw-removed', |
| 161 | + $wgLang->formatNum( count( $current ) ) |
| 162 | + ); |
| 163 | + $this->showTitles( $current, $this->successMessage, $wgUser->getSkin() ); |
| 164 | + } |
| 165 | + return true; |
| 166 | + } |
| 167 | + |
| 168 | + /** |
| 169 | + * Print out a list of linked titles |
| 170 | + * |
| 171 | + * $titles can be an array of strings or Title objects; the former |
| 172 | + * is preferred, since Titles are very memory-heavy |
| 173 | + * |
| 174 | + * @param $titles array of strings, or Title objects |
| 175 | + * @param $output String |
| 176 | + * @param $skin Skin |
| 177 | + */ |
| 178 | + private function showTitles( $titles, &$output, $skin ) { |
| 179 | + $talk = wfMsgHtml( 'talkpagelinktext' ); |
| 180 | + // Do a batch existence check |
| 181 | + $batch = new LinkBatch(); |
| 182 | + foreach( $titles as $title ) { |
| 183 | + if( !$title instanceof Title ) { |
| 184 | + $title = Title::newFromText( $title ); |
| 185 | + } |
| 186 | + if( $title instanceof Title ) { |
| 187 | + $batch->addObj( $title ); |
| 188 | + $batch->addObj( $title->getTalkPage() ); |
| 189 | + } |
| 190 | + } |
| 191 | + $batch->execute(); |
| 192 | + // Print out the list |
| 193 | + $output .= "<ul>\n"; |
| 194 | + foreach( $titles as $title ) { |
| 195 | + if( !$title instanceof Title ) { |
| 196 | + $title = Title::newFromText( $title ); |
| 197 | + } |
| 198 | + if( $title instanceof Title ) { |
| 199 | + $output .= "<li>" |
| 200 | + . $skin->link( $title ) |
| 201 | + . ' (' . $skin->link( $title->getTalkPage(), $talk ) |
| 202 | + . ")</li>\n"; |
| 203 | + } |
| 204 | + } |
| 205 | + $output .= "</ul>\n"; |
| 206 | + } |
| 207 | + |
| 208 | + /** |
| 209 | + * Count the number of titles on a user's watchlist, excluding talk pages |
| 210 | + * |
| 211 | + * @param $user User |
| 212 | + * @return int |
| 213 | + */ |
| 214 | + private function countWatchlist( $user ) { |
| 215 | + $dbr = wfGetDB( DB_MASTER ); |
| 216 | + $res = $dbr->select( 'watchlist', 'COUNT(*) AS count', array( 'wl_user' => $user->getId() ), __METHOD__ ); |
| 217 | + $row = $dbr->fetchObject( $res ); |
| 218 | + return ceil( $row->count / 2 ); // Paranoia |
| 219 | + } |
| 220 | + |
| 221 | + /** |
| 222 | + * Prepare a list of titles on a user's watchlist (excluding talk pages) |
| 223 | + * and return an array of (prefixed) strings |
| 224 | + * |
| 225 | + * @param $user User |
| 226 | + * @return array |
| 227 | + */ |
| 228 | + private function getWatchlist( $user ) { |
| 229 | + $list = array(); |
| 230 | + $dbr = wfGetDB( DB_MASTER ); |
| 231 | + $res = $dbr->select( |
| 232 | + 'watchlist', |
| 233 | + '*', |
| 234 | + array( |
| 235 | + 'wl_user' => $user->getId(), |
| 236 | + ), |
| 237 | + __METHOD__ |
| 238 | + ); |
| 239 | + if( $res->numRows() > 0 ) { |
| 240 | + foreach ( $res as $row ) { |
| 241 | + $title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title ); |
| 242 | + if( $title instanceof Title && !$title->isTalkPage() ) |
| 243 | + $list[] = $title->getPrefixedText(); |
| 244 | + } |
| 245 | + $res->free(); |
| 246 | + } |
| 247 | + return $list; |
| 248 | + } |
| 249 | + |
| 250 | + /** |
| 251 | + * Get a list of titles on a user's watchlist, excluding talk pages, |
| 252 | + * and return as a two-dimensional array with namespace, title and |
| 253 | + * redirect status |
| 254 | + * |
| 255 | + * @param $user User |
| 256 | + * @return array |
| 257 | + */ |
| 258 | + private function getWatchlistInfo( $user ) { |
| 259 | + $titles = array(); |
| 260 | + $dbr = wfGetDB( DB_MASTER ); |
| 261 | + |
| 262 | + $res = $dbr->select( |
| 263 | + array( 'watchlist', 'page' ), |
| 264 | + array( |
| 265 | + 'wl_namespace', |
| 266 | + 'wl_title', |
| 267 | + 'page_id', |
| 268 | + 'page_len', |
| 269 | + 'page_is_redirect', |
| 270 | + 'page_latest' |
| 271 | + ), |
| 272 | + array( 'wl_user' => $user->getId() ), |
| 273 | + __METHOD__, |
| 274 | + array( 'ORDER BY' => 'wl_namespace, wl_title' ), |
| 275 | + array( 'page' => array( |
| 276 | + 'LEFT JOIN', |
| 277 | + 'wl_namespace = page_namespace AND wl_title = page_title' |
| 278 | + ) ) |
| 279 | + ); |
| 280 | + |
| 281 | + if( $res && $dbr->numRows( $res ) > 0 ) { |
| 282 | + $cache = LinkCache::singleton(); |
| 283 | + foreach ( $res as $row ) { |
| 284 | + $title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title ); |
| 285 | + if( $title instanceof Title ) { |
| 286 | + // Update the link cache while we're at it |
| 287 | + if( $row->page_id ) { |
| 288 | + $cache->addGoodLinkObj( $row->page_id, $title, $row->page_len, $row->page_is_redirect, $row->page_latest ); |
| 289 | + } else { |
| 290 | + $cache->addBadLinkObj( $title ); |
| 291 | + } |
| 292 | + // Ignore non-talk |
| 293 | + if( !$title->isTalkPage() ) { |
| 294 | + $titles[$row->wl_namespace][$row->wl_title] = $row->page_is_redirect; |
| 295 | + } |
| 296 | + } |
| 297 | + } |
| 298 | + } |
| 299 | + return $titles; |
| 300 | + } |
| 301 | + |
| 302 | + /** |
| 303 | + * Show a message indicating the number of items on the user's watchlist, |
| 304 | + * and return this count for additional checking |
| 305 | + * |
| 306 | + * @param $output OutputPage |
| 307 | + * @param $user User |
| 308 | + * @return int |
| 309 | + */ |
| 310 | + private function showItemCount( $output, $user ) { |
| 311 | + if( ( $count = $this->countWatchlist( $user ) ) > 0 ) { |
| 312 | + $output->addHTML( wfMsgExt( 'watchlistedit-numitems', 'parse', |
| 313 | + $GLOBALS['wgLang']->formatNum( $count ) ) ); |
| 314 | + } else { |
| 315 | + $output->addHTML( wfMsgExt( 'watchlistedit-noitems', 'parse' ) ); |
| 316 | + } |
| 317 | + return $count; |
| 318 | + } |
| 319 | + |
| 320 | + /** |
| 321 | + * Remove all titles from a user's watchlist |
| 322 | + * |
| 323 | + * @param $user User |
| 324 | + */ |
| 325 | + private function clearWatchlist( $user ) { |
| 326 | + $dbw = wfGetDB( DB_MASTER ); |
| 327 | + $dbw->delete( |
| 328 | + 'watchlist', |
| 329 | + array( 'wl_user' => $user->getId() ), |
| 330 | + __METHOD__ |
| 331 | + ); |
| 332 | + } |
| 333 | + |
| 334 | + /** |
| 335 | + * Add a list of titles to a user's watchlist |
| 336 | + * |
| 337 | + * $titles can be an array of strings or Title objects; the former |
| 338 | + * is preferred, since Titles are very memory-heavy |
| 339 | + * |
| 340 | + * @param $titles Array of strings, or Title objects |
| 341 | + * @param $user User |
| 342 | + */ |
| 343 | + private function watchTitles( $titles, $user ) { |
| 344 | + $dbw = wfGetDB( DB_MASTER ); |
| 345 | + $rows = array(); |
| 346 | + foreach( $titles as $title ) { |
| 347 | + if( !$title instanceof Title ) { |
| 348 | + $title = Title::newFromText( $title ); |
| 349 | + } |
| 350 | + if( $title instanceof Title ) { |
| 351 | + $rows[] = array( |
| 352 | + 'wl_user' => $user->getId(), |
| 353 | + 'wl_namespace' => ( $title->getNamespace() & ~1 ), |
| 354 | + 'wl_title' => $title->getDBkey(), |
| 355 | + 'wl_notificationtimestamp' => null, |
| 356 | + ); |
| 357 | + $rows[] = array( |
| 358 | + 'wl_user' => $user->getId(), |
| 359 | + 'wl_namespace' => ( $title->getNamespace() | 1 ), |
| 360 | + 'wl_title' => $title->getDBkey(), |
| 361 | + 'wl_notificationtimestamp' => null, |
| 362 | + ); |
| 363 | + } |
| 364 | + } |
| 365 | + $dbw->insert( 'watchlist', $rows, __METHOD__, 'IGNORE' ); |
| 366 | + } |
| 367 | + |
| 368 | + /** |
| 369 | + * Remove a list of titles from a user's watchlist |
| 370 | + * |
| 371 | + * $titles can be an array of strings or Title objects; the former |
| 372 | + * is preferred, since Titles are very memory-heavy |
| 373 | + * |
| 374 | + * @param $titles Array of strings, or Title objects |
| 375 | + * @param $user User |
| 376 | + */ |
| 377 | + private function unwatchTitles( $titles, $user ) { |
| 378 | + $dbw = wfGetDB( DB_MASTER ); |
| 379 | + foreach( $titles as $title ) { |
| 380 | + if( !$title instanceof Title ) { |
| 381 | + $title = Title::newFromText( $title ); |
| 382 | + } |
| 383 | + if( $title instanceof Title ) { |
| 384 | + $dbw->delete( |
| 385 | + 'watchlist', |
| 386 | + array( |
| 387 | + 'wl_user' => $user->getId(), |
| 388 | + 'wl_namespace' => ( $title->getNamespace() & ~1 ), |
| 389 | + 'wl_title' => $title->getDBkey(), |
| 390 | + ), |
| 391 | + __METHOD__ |
| 392 | + ); |
| 393 | + $dbw->delete( |
| 394 | + 'watchlist', |
| 395 | + array( |
| 396 | + 'wl_user' => $user->getId(), |
| 397 | + 'wl_namespace' => ( $title->getNamespace() | 1 ), |
| 398 | + 'wl_title' => $title->getDBkey(), |
| 399 | + ), |
| 400 | + __METHOD__ |
| 401 | + ); |
| 402 | + $article = new Article($title); |
| 403 | + wfRunHooks('UnwatchArticleComplete',array(&$user,&$article)); |
| 404 | + } |
| 405 | + } |
| 406 | + } |
| 407 | + |
| 408 | + public function submitNormal( $data ) { |
| 409 | + global $wgUser; |
| 410 | + $removed = array(); |
| 411 | + |
| 412 | + foreach( $data as $titles ) { |
| 413 | + $this->unwatchTitles( $titles, $wgUser ); |
| 414 | + $removed += $titles; |
| 415 | + } |
| 416 | + |
| 417 | + if( count( $removed ) > 0 ) { |
| 418 | + global $wgLang; |
| 419 | + $this->successMessage = wfMessage( |
| 420 | + 'watchlistedit-normal-done', |
| 421 | + $wgLang->formatNum( count( $removed ) ) |
| 422 | + ); |
| 423 | + $this->showTitles( $removed, $this->successMessage, $wgUser->getSkin() ); |
| 424 | + return true; |
| 425 | + } else { |
| 426 | + return false; |
| 427 | + } |
| 428 | + } |
| 429 | + |
| 430 | + /** |
| 431 | + * Get the standard watchlist editing form |
| 432 | + * |
| 433 | + * @param $user User |
| 434 | + * @return HTMLForm |
| 435 | + */ |
| 436 | + protected function getNormalForm( $user ){ |
| 437 | + global $wgContLang; |
| 438 | + $skin = $user->getSkin(); |
| 439 | + $fields = array(); |
| 440 | + |
| 441 | + foreach( $this->getWatchlistInfo( $user ) as $namespace => $pages ){ |
| 442 | + |
| 443 | + $namespace == NS_MAIN |
| 444 | + ? wfMsgHtml( 'blanknamespace' ) |
| 445 | + : htmlspecialchars( $wgContLang->getFormattedNsText( $namespace ) ); |
| 446 | + |
| 447 | + $fields['TitlesNs'.$namespace] = array( |
| 448 | + 'type' => 'multiselect', |
| 449 | + 'options' => array(), |
| 450 | + 'section' => "ns$namespace", |
| 451 | + ); |
| 452 | + |
| 453 | + foreach( $pages as $dbkey => $redirect ){ |
| 454 | + $title = Title::makeTitleSafe( $namespace, $dbkey ); |
| 455 | + $text = $this->buildRemoveLine( $title, $redirect, $skin ); |
| 456 | + $fields['TitlesNs'.$namespace]['options'][$text] = $title->getEscapedText(); |
| 457 | + } |
| 458 | + } |
| 459 | + |
| 460 | + $form = new EditWatchlistNormalHTMLForm( $fields ); |
| 461 | + $form->setTitle( $this->getTitle() ); |
| 462 | + $form->setSubmitText( wfMessage( 'watchlistedit-normal-submit' )->text() ); |
| 463 | + $form->setWrapperLegend( wfMessage( 'watchlistedit-normal-legend' )->text() ); |
| 464 | + $form->addHeaderText( wfMessage( 'watchlistedit-normal-explain' )->parse() ); |
| 465 | + $form->setSubmitCallback( array( $this, 'submitNormal' ) ); |
| 466 | + return $form; |
| 467 | + } |
| 468 | + |
| 469 | + /** |
| 470 | + * Build the label for a checkbox, with a link to the title, and various additional bits |
| 471 | + * |
| 472 | + * @param $title Title |
| 473 | + * @param $redirect bool |
| 474 | + * @param $skin Skin |
| 475 | + * @return string |
| 476 | + */ |
| 477 | + private function buildRemoveLine( $title, $redirect, $skin ) { |
| 478 | + global $wgLang; |
| 479 | + |
| 480 | + $link = $skin->link( $title ); |
| 481 | + if( $redirect ) { |
| 482 | + $link = '<span class="watchlistredir">' . $link . '</span>'; |
| 483 | + } |
| 484 | + $tools[] = $skin->link( $title->getTalkPage(), wfMsgHtml( 'talkpagelinktext' ) ); |
| 485 | + if( $title->exists() ) { |
| 486 | + $tools[] = $skin->link( |
| 487 | + $title, |
| 488 | + wfMsgHtml( 'history_short' ), |
| 489 | + array(), |
| 490 | + array( 'action' => 'history' ), |
| 491 | + array( 'known', 'noclasses' ) |
| 492 | + ); |
| 493 | + } |
| 494 | + if( $title->getNamespace() == NS_USER && !$title->isSubpage() ) { |
| 495 | + $tools[] = $skin->link( |
| 496 | + SpecialPage::getTitleFor( 'Contributions', $title->getText() ), |
| 497 | + wfMsgHtml( 'contributions' ), |
| 498 | + array(), |
| 499 | + array(), |
| 500 | + array( 'known', 'noclasses' ) |
| 501 | + ); |
| 502 | + } |
| 503 | + |
| 504 | + wfRunHooks( 'WatchlistEditorBuildRemoveLine', array( &$tools, $title, $redirect, $skin ) ); |
| 505 | + |
| 506 | + return $link . " (" . $wgLang->pipeList( $tools ) . ")"; |
| 507 | + } |
| 508 | + |
| 509 | + /** |
| 510 | + * Get a form for editing the watchlist in "raw" mode |
| 511 | + * |
| 512 | + * @param $user User |
| 513 | + * @return HTMLForm |
| 514 | + */ |
| 515 | + protected function getRawForm( $user ){ |
| 516 | + $titles = implode( array_map( 'htmlspecialchars', $this->getWatchlist( $user ) ), "\n" ); |
| 517 | + $fields = array( |
| 518 | + 'Titles' => array( |
| 519 | + 'type' => 'textarea', |
| 520 | + 'label-message' => 'watchlistedit-raw-titles', |
| 521 | + 'default' => $titles, |
| 522 | + ), |
| 523 | + ); |
| 524 | + $form = new HTMLForm( $fields ); |
| 525 | + $form->setTitle( $this->getTitle( 'raw' ) ); |
| 526 | + $form->setSubmitText( wfMessage( 'watchlistedit-raw-submit' )->text() ); |
| 527 | + $form->setWrapperLegend( wfMessage( 'watchlistedit-raw-legend' )->text() ); |
| 528 | + $form->addHeaderText( wfMessage( 'watchlistedit-raw-explain' )->parse() ); |
| 529 | + $form->setSubmitCallback( array( $this, 'submitRaw' ) ); |
| 530 | + return $form; |
| 531 | + } |
| 532 | + |
| 533 | + /** |
| 534 | + * Determine whether we are editing the watchlist, and if so, what |
| 535 | + * kind of editing operation |
| 536 | + * |
| 537 | + * @param $request WebRequest |
| 538 | + * @param $par mixed |
| 539 | + * @return int |
| 540 | + */ |
| 541 | + public static function getMode( $request, $par ) { |
| 542 | + $mode = strtolower( $request->getVal( 'action', $par ) ); |
| 543 | + switch( $mode ) { |
| 544 | + case 'clear': |
| 545 | + case self::EDIT_CLEAR: |
| 546 | + return self::EDIT_CLEAR; |
| 547 | + |
| 548 | + case 'raw': |
| 549 | + case self::EDIT_RAW: |
| 550 | + return self::EDIT_RAW; |
| 551 | + |
| 552 | + case 'edit': |
| 553 | + case self::EDIT_NORMAL: |
| 554 | + return self::EDIT_NORMAL; |
| 555 | + |
| 556 | + default: |
| 557 | + return false; |
| 558 | + } |
| 559 | + } |
| 560 | + |
| 561 | + /** |
| 562 | + * Build a set of links for convenient navigation |
| 563 | + * between watchlist viewing and editing modes |
| 564 | + * |
| 565 | + * @param $skin Skin to use |
| 566 | + * @return string |
| 567 | + */ |
| 568 | + public static function buildTools( $skin ) { |
| 569 | + global $wgLang; |
| 570 | + |
| 571 | + $tools = array(); |
| 572 | + $modes = array( |
| 573 | + 'view' => array( 'Watchlist', false ), |
| 574 | + 'edit' => array( 'EditWatchlist', false ), |
| 575 | + 'raw' => array( 'EditWatchlist', 'raw' ), |
| 576 | + ); |
| 577 | + foreach( $modes as $mode => $arr ) { |
| 578 | + // can use messages 'watchlisttools-view', 'watchlisttools-edit', 'watchlisttools-raw' |
| 579 | + $tools[] = $skin->linkKnown( |
| 580 | + SpecialPage::getTitleFor( $arr[0], $arr[1] ), |
| 581 | + wfMsgHtml( "watchlisttools-{$mode}" ) |
| 582 | + ); |
| 583 | + } |
| 584 | + return Html::rawElement( 'span', |
| 585 | + array( 'class' => 'mw-watchlist-toollinks' ), |
| 586 | + wfMsg( 'parentheses', $wgLang->pipeList( $tools ) ) ); |
| 587 | + } |
| 588 | +} |
| 589 | + |
| 590 | +# B/C since 1.18 |
| 591 | +class WatchlistEditor extends SpecialEditWatchlist {} |
| 592 | + |
| 593 | +/** |
| 594 | + * Extend HTMLForm purely so we can have a more sane way of getting the section headers |
| 595 | + */ |
| 596 | +class EditWatchlistNormalHTMLForm extends HTMLForm { |
| 597 | + public function getLegend( $namespace ){ |
| 598 | + global $wgLang; |
| 599 | + $namespace = substr( $namespace, 2 ); |
| 600 | + return $namespace == NS_MAIN |
| 601 | + ? wfMsgHtml( 'blanknamespace' ) |
| 602 | + : htmlspecialchars( $wgLang->getFormattedNsText( $namespace ) ); |
| 603 | + } |
| 604 | +} |
\ No newline at end of file |
Property changes on: trunk/phase3/includes/specials/SpecialEditWatchlist.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 605 | + native |
Index: trunk/phase3/includes/specials/SpecialWatchlist.php |
— | — | @@ -68,12 +68,28 @@ |
69 | 69 | |
70 | 70 | $wgOut->setPageTitle( wfMsg( 'watchlist' ) ); |
71 | 71 | |
72 | | - $sub = wfMsgExt( 'watchlistfor2', array( 'parseinline', 'replaceafter' ), $wgUser->getName(), WatchlistEditor::buildTools( $wgUser->getSkin() ) ); |
| 72 | + $sub = wfMsgExt( |
| 73 | + 'watchlistfor2', |
| 74 | + array( 'parseinline', 'replaceafter' ), |
| 75 | + $wgUser->getName(), |
| 76 | + SpecialEditWatchlist::buildTools( $wgUser->getSkin() ) |
| 77 | + ); |
73 | 78 | $wgOut->setSubtitle( $sub ); |
74 | 79 | |
75 | | - if( ( $mode = WatchlistEditor::getMode( $wgRequest, $par ) ) !== false ) { |
76 | | - $editor = new WatchlistEditor(); |
77 | | - $editor->execute( $wgUser, $wgOut, $wgRequest, $mode ); |
| 80 | + if( ( $mode = SpecialEditWatchlist::getMode( $wgRequest, $par ) ) !== false ) { |
| 81 | + # TODO: localise? |
| 82 | + switch( $mode ){ |
| 83 | + case SpecialEditWatchlist::EDIT_CLEAR: |
| 84 | + $mode = 'clear'; |
| 85 | + break; |
| 86 | + case SpecialEditWatchlist::EDIT_RAW: |
| 87 | + $mode = 'raw'; |
| 88 | + break; |
| 89 | + default: |
| 90 | + $mode = null; |
| 91 | + } |
| 92 | + $title = SpecialPage::getTitleFor( 'EditWatchlist', $mode ); |
| 93 | + $wgOut->redirect( $title->getLocalUrl() ); |
78 | 94 | return; |
79 | 95 | } |
80 | 96 | |
Index: trunk/phase3/includes/SpecialPage.php |
— | — | @@ -150,6 +150,7 @@ |
151 | 151 | 'Activeusers' => 'SpecialActiveUsers', |
152 | 152 | 'Userrights' => 'UserrightsPage', |
153 | 153 | 'DisableAccount' => 'SpecialDisableAccount', |
| 154 | + 'EditWatchlist' => 'SpecialEditWatchlist', |
154 | 155 | |
155 | 156 | # Recent changes and logs |
156 | 157 | 'Newimages' => array( 'IncludableSpecialPage', 'Newimages' ), |
Index: trunk/phase3/languages/messages/MessagesEn.php |
— | — | @@ -398,6 +398,7 @@ |
399 | 399 | 'DisableAccount' => array( 'DisableAccount' ), |
400 | 400 | 'Disambiguations' => array( 'Disambiguations' ), |
401 | 401 | 'DoubleRedirects' => array( 'DoubleRedirects' ), |
| 402 | + 'EditWatchlist' => array( 'EditWatchlist' ), |
402 | 403 | 'Emailuser' => array( 'EmailUser' ), |
403 | 404 | 'Export' => array( 'Export' ), |
404 | 405 | 'Fewestrevisions' => array( 'FewestRevisions' ), |
— | — | @@ -4234,7 +4235,7 @@ |
4235 | 4236 | 'watchlistedit-normal-legend' => 'Remove titles from watchlist', |
4236 | 4237 | 'watchlistedit-normal-explain' => 'Titles on your watchlist are shown below. |
4237 | 4238 | To remove a title, check the box next to it, and click "{{int:Watchlistedit-normal-submit}}". |
4238 | | -You can also [[Special:Watchlist/raw|edit the raw list]].', |
| 4239 | +You can also [[Special:EditWatchlist/raw|edit the raw list]].', |
4239 | 4240 | 'watchlistedit-normal-submit' => 'Remove titles', |
4240 | 4241 | 'watchlistedit-normal-done' => '{{PLURAL:$1|1 title was|$1 titles were}} removed from your watchlist:', |
4241 | 4242 | 'watchlistedit-raw-title' => 'Edit raw watchlist', |
— | — | @@ -4242,7 +4243,7 @@ |
4243 | 4244 | 'watchlistedit-raw-explain' => 'Titles on your watchlist are shown below, and can be edited by adding to and removing from the list; |
4244 | 4245 | one title per line. |
4245 | 4246 | When finished, click "{{int:Watchlistedit-raw-submit}}". |
4246 | | -You can also [[Special:Watchlist/edit|use the standard editor]].', |
| 4247 | +You can also [[Special:EditWatchlist|use the standard editor]].', |
4247 | 4248 | 'watchlistedit-raw-titles' => 'Titles:', |
4248 | 4249 | 'watchlistedit-raw-submit' => 'Update watchlist', |
4249 | 4250 | 'watchlistedit-raw-done' => 'Your watchlist has been updated.', |