Index: trunk/phase3/RELEASE-NOTES-1.19 |
— | — | @@ -246,6 +246,7 @@ |
247 | 247 | getText() on a non-object |
248 | 248 | * (bug 31676) Group dynamically inserted CSS into a single <style> tag, to work |
249 | 249 | around a bug where not all styles were applied in Internet Explorer |
| 250 | +* (bug 28936, bug 5280) Broken or invalid titles can't be removed from watchlist. |
250 | 251 | |
251 | 252 | === API changes in 1.19 === |
252 | 253 | * Made action=edit less likely to return "unknownerror", by returning the actual error |
Index: trunk/phase3/includes/User.php |
— | — | @@ -2602,14 +2602,6 @@ |
2603 | 2603 | } |
2604 | 2604 | |
2605 | 2605 | /** |
2606 | | - * Cleans up watchlist by removing invalid entries from it |
2607 | | - */ |
2608 | | - public function cleanupWatchlist() { |
2609 | | - $dbw = wfGetDB( DB_MASTER ); |
2610 | | - $dbw->delete( 'watchlist', array( 'wl_namespace < 0', 'wl_user' => $this->getId() ), __METHOD__ ); |
2611 | | - } |
2612 | | - |
2613 | | - /** |
2614 | 2606 | * Clear the user's notification timestamp for the given title. |
2615 | 2607 | * If e-notif e-mails are on, they will receive notification mails on |
2616 | 2608 | * the next change of the page if it's watched etc. |
Index: trunk/phase3/includes/specials/SpecialEditWatchlist.php |
— | — | @@ -23,6 +23,8 @@ |
24 | 24 | |
25 | 25 | protected $toc; |
26 | 26 | |
| 27 | + private $badItems = array(); |
| 28 | + |
27 | 29 | public function __construct(){ |
28 | 30 | parent::__construct( 'EditWatchlist' ); |
29 | 31 | } |
— | — | @@ -224,11 +226,15 @@ |
225 | 227 | if( $res->numRows() > 0 ) { |
226 | 228 | foreach ( $res as $row ) { |
227 | 229 | $title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title ); |
228 | | - if( $title instanceof Title && !$title->isTalkPage() ) |
| 230 | + if ( $this->checkTitle( $title, $row->wl_namespace, $row->wl_title ) |
| 231 | + && !$title->isTalkPage() |
| 232 | + ) { |
229 | 233 | $list[] = $title->getPrefixedText(); |
| 234 | + } |
230 | 235 | } |
231 | 236 | $res->free(); |
232 | 237 | } |
| 238 | + $this->cleanupWatchlist(); |
233 | 239 | return $list; |
234 | 240 | } |
235 | 241 | |
— | — | @@ -263,6 +269,60 @@ |
264 | 270 | } |
265 | 271 | |
266 | 272 | /** |
| 273 | + * Validates watchlist entry |
| 274 | + * |
| 275 | + * @param Title $title |
| 276 | + * @param int $namespace |
| 277 | + * @param String $dbKey |
| 278 | + * @return bool: Whether this item is valid |
| 279 | + */ |
| 280 | + private function checkTitle( $title, $namespace, $dbKey ) { |
| 281 | + if ( $title |
| 282 | + && ( $title->isExternal() |
| 283 | + || $title->getNamespace() < 0 |
| 284 | + ) |
| 285 | + ) { |
| 286 | + $title = false; // unrecoverable |
| 287 | + } |
| 288 | + if ( !$title |
| 289 | + || $title->getNamespace() != $namespace |
| 290 | + || $title->getDBkey() != $dbKey |
| 291 | + ) { |
| 292 | + $this->badItems[] = array( $title, $namespace, $dbKey ); |
| 293 | + } |
| 294 | + return (bool)$title; |
| 295 | + } |
| 296 | + |
| 297 | + /** |
| 298 | + * Attempts to clean up broken items |
| 299 | + */ |
| 300 | + private function cleanupWatchlist() { |
| 301 | + if ( count( $this->badItems ) ) { |
| 302 | + $dbw = wfGetDB( DB_MASTER ); |
| 303 | + } |
| 304 | + foreach ( $this->badItems as $row ) { |
| 305 | + list( $title, $namespace, $dbKey ) = $row; |
| 306 | + wfDebug( "User {$this->getUser()} has broken watchlist item ns($namespace):$dbKey, " |
| 307 | + . ( $title ? 'cleaning up' : 'deleting' ) . ".\n" |
| 308 | + ); |
| 309 | + |
| 310 | + $dbw->delete( 'watchlist', |
| 311 | + array( |
| 312 | + 'wl_user' => $this->getUser()->getId(), |
| 313 | + 'wl_namespace' => $namespace, |
| 314 | + 'wl_title' => $dbKey, |
| 315 | + ), |
| 316 | + __METHOD__ |
| 317 | + ); |
| 318 | + |
| 319 | + // Can't just do an UPDATE instead of DELETE/INSERT due to unique index |
| 320 | + if ( $title ) { |
| 321 | + $this->getUser()->addWatch( $title ); |
| 322 | + } |
| 323 | + } |
| 324 | + } |
| 325 | + |
| 326 | + /** |
267 | 327 | * Remove all titles from a user's watchlist |
268 | 328 | */ |
269 | 329 | private function clearWatchlist() { |
— | — | @@ -375,30 +435,25 @@ |
376 | 436 | $fields = array(); |
377 | 437 | $count = 0; |
378 | 438 | |
379 | | - $haveInvalidNamespaces = false; |
380 | 439 | foreach( $this->getWatchlistInfo() as $namespace => $pages ){ |
381 | | - if ( $namespace < 0 ) { |
382 | | - $haveInvalidNamespaces = true; |
383 | | - continue; |
| 440 | + if ( $namespace >= 0 ) { |
| 441 | + $fields['TitlesNs'.$namespace] = array( |
| 442 | + 'class' => 'EditWatchlistCheckboxSeriesField', |
| 443 | + 'options' => array(), |
| 444 | + 'section' => "ns$namespace", |
| 445 | + ); |
384 | 446 | } |
385 | 447 | |
386 | | - $fields['TitlesNs'.$namespace] = array( |
387 | | - 'class' => 'EditWatchlistCheckboxSeriesField', |
388 | | - 'options' => array(), |
389 | | - 'section' => "ns$namespace", |
390 | | - ); |
391 | | - |
392 | 448 | foreach( array_keys( $pages ) as $dbkey ){ |
393 | 449 | $title = Title::makeTitleSafe( $namespace, $dbkey ); |
394 | | - $text = $this->buildRemoveLine( $title ); |
395 | | - $fields['TitlesNs'.$namespace]['options'][$text] = $title->getEscapedText(); |
396 | | - $count++; |
| 450 | + if ( $this->checkTitle( $title, $namespace, $dbkey ) ) { |
| 451 | + $text = $this->buildRemoveLine( $title ); |
| 452 | + $fields['TitlesNs'.$namespace]['options'][$text] = $title->getEscapedText(); |
| 453 | + $count++; |
| 454 | + } |
397 | 455 | } |
398 | 456 | } |
399 | | - if ( $haveInvalidNamespaces ) { |
400 | | - wfDebug( "User {$this->getContext()->getUser()->getId()} has invalid watchlist entries, cleaning up...\n" ); |
401 | | - $this->getContext()->getUser()->cleanupWatchlist(); |
402 | | - } |
| 457 | + $this->cleanupWatchlist(); |
403 | 458 | |
404 | 459 | if ( count( $fields ) > 1 && $count > 30 ) { |
405 | 460 | $this->toc = Linker::tocIndent(); |