MediaWiki
REL1_23
|
00001 <?php 00037 class SpecialEditWatchlist extends UnlistedSpecialPage { 00042 const EDIT_CLEAR = 1; 00043 const EDIT_RAW = 2; 00044 const EDIT_NORMAL = 3; 00045 00046 protected $successMessage; 00047 00048 protected $toc; 00049 00050 private $badItems = array(); 00051 00052 public function __construct() { 00053 parent::__construct( 'EditWatchlist', 'editmywatchlist' ); 00054 } 00055 00061 public function execute( $mode ) { 00062 $this->setHeaders(); 00063 00064 # Anons don't get a watchlist 00065 $this->requireLogin( 'watchlistanontext' ); 00066 00067 $out = $this->getOutput(); 00068 00069 $this->checkPermissions(); 00070 $this->checkReadOnly(); 00071 00072 $this->outputHeader(); 00073 00074 $out->addSubtitle( $this->msg( 'watchlistfor2', $this->getUser()->getName() ) 00075 ->rawParams( SpecialEditWatchlist::buildTools( null ) ) ); 00076 00077 # B/C: $mode used to be waaay down the parameter list, and the first parameter 00078 # was $wgUser 00079 if ( $mode instanceof User ) { 00080 $args = func_get_args(); 00081 if ( count( $args ) >= 4 ) { 00082 $mode = $args[3]; 00083 } 00084 } 00085 $mode = self::getMode( $this->getRequest(), $mode ); 00086 00087 switch ( $mode ) { 00088 case self::EDIT_RAW: 00089 $out->setPageTitle( $this->msg( 'watchlistedit-raw-title' ) ); 00090 $form = $this->getRawForm(); 00091 if ( $form->show() ) { 00092 $out->addHTML( $this->successMessage ); 00093 $out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) ); 00094 } 00095 break; 00096 case self::EDIT_CLEAR: 00097 $out->setPageTitle( $this->msg( 'watchlistedit-clear-title' ) ); 00098 $form = $this->getClearForm(); 00099 if ( $form->show() ) { 00100 $out->addHTML( $this->successMessage ); 00101 $out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) ); 00102 } 00103 break; 00104 00105 case self::EDIT_NORMAL: 00106 default: 00107 $out->setPageTitle( $this->msg( 'watchlistedit-normal-title' ) ); 00108 $form = $this->getNormalForm(); 00109 if ( $form->show() ) { 00110 $out->addHTML( $this->successMessage ); 00111 $out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) ); 00112 } elseif ( $this->toc !== false ) { 00113 $out->prependHTML( $this->toc ); 00114 } 00115 break; 00116 } 00117 } 00118 00126 private function extractTitles( $list ) { 00127 $list = explode( "\n", trim( $list ) ); 00128 if ( !is_array( $list ) ) { 00129 return array(); 00130 } 00131 00132 $titles = array(); 00133 00134 foreach ( $list as $text ) { 00135 $text = trim( $text ); 00136 if ( strlen( $text ) > 0 ) { 00137 $title = Title::newFromText( $text ); 00138 if ( $title instanceof Title && $title->isWatchable() ) { 00139 $titles[] = $title; 00140 } 00141 } 00142 } 00143 00144 GenderCache::singleton()->doTitlesArray( $titles ); 00145 00146 $list = array(); 00148 foreach ( $titles as $title ) { 00149 $list[] = $title->getPrefixedText(); 00150 } 00151 00152 return array_unique( $list ); 00153 } 00154 00155 public function submitRaw( $data ) { 00156 $wanted = $this->extractTitles( $data['Titles'] ); 00157 $current = $this->getWatchlist(); 00158 00159 if ( count( $wanted ) > 0 ) { 00160 $toWatch = array_diff( $wanted, $current ); 00161 $toUnwatch = array_diff( $current, $wanted ); 00162 $this->watchTitles( $toWatch ); 00163 $this->unwatchTitles( $toUnwatch ); 00164 $this->getUser()->invalidateCache(); 00165 00166 if ( count( $toWatch ) > 0 || count( $toUnwatch ) > 0 ) { 00167 $this->successMessage = $this->msg( 'watchlistedit-raw-done' )->parse(); 00168 } else { 00169 return false; 00170 } 00171 00172 if ( count( $toWatch ) > 0 ) { 00173 $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-added' ) 00174 ->numParams( count( $toWatch ) )->parse(); 00175 $this->showTitles( $toWatch, $this->successMessage ); 00176 } 00177 00178 if ( count( $toUnwatch ) > 0 ) { 00179 $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-removed' ) 00180 ->numParams( count( $toUnwatch ) )->parse(); 00181 $this->showTitles( $toUnwatch, $this->successMessage ); 00182 } 00183 } else { 00184 $this->clearWatchlist(); 00185 $this->getUser()->invalidateCache(); 00186 00187 if ( count( $current ) > 0 ) { 00188 $this->successMessage = $this->msg( 'watchlistedit-raw-done' )->parse(); 00189 } else { 00190 return false; 00191 } 00192 00193 $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-removed' ) 00194 ->numParams( count( $current ) )->parse(); 00195 $this->showTitles( $current, $this->successMessage ); 00196 } 00197 00198 return true; 00199 } 00200 00201 public function submitClear( $data ) { 00202 $current = $this->getWatchlist(); 00203 $this->clearWatchlist(); 00204 $this->getUser()->invalidateCache(); 00205 $this->successMessage = $this->msg( 'watchlistedit-clear-done' )->parse(); 00206 $this->successMessage .= ' ' . $this->msg( 'watchlistedit-clear-removed' ) 00207 ->numParams( count( $current ) )->parse(); 00208 $this->showTitles( $current, $this->successMessage ); 00209 00210 return true; 00211 } 00212 00222 private function showTitles( $titles, &$output ) { 00223 $talk = $this->msg( 'talkpagelinktext' )->escaped(); 00224 // Do a batch existence check 00225 $batch = new LinkBatch(); 00226 if (count($titles) >= 100) { 00227 $output = wfMessage( 'watchlistedit-too-many' )->parse(); 00228 return; 00229 } 00230 foreach ( $titles as $title ) { 00231 if ( !$title instanceof Title ) { 00232 $title = Title::newFromText( $title ); 00233 } 00234 00235 if ( $title instanceof Title ) { 00236 $batch->addObj( $title ); 00237 $batch->addObj( $title->getTalkPage() ); 00238 } 00239 } 00240 00241 $batch->execute(); 00242 00243 // Print out the list 00244 $output .= "<ul>\n"; 00245 00246 foreach ( $titles as $title ) { 00247 if ( !$title instanceof Title ) { 00248 $title = Title::newFromText( $title ); 00249 } 00250 00251 if ( $title instanceof Title ) { 00252 $output .= "<li>" 00253 . Linker::link( $title ) 00254 . ' (' . Linker::link( $title->getTalkPage(), $talk ) 00255 . ")</li>\n"; 00256 } 00257 } 00258 00259 $output .= "</ul>\n"; 00260 } 00261 00268 private function getWatchlist() { 00269 $list = array(); 00270 $dbr = wfGetDB( DB_MASTER ); 00271 00272 $res = $dbr->select( 00273 'watchlist', 00274 array( 00275 'wl_namespace', 'wl_title' 00276 ), array( 00277 'wl_user' => $this->getUser()->getId(), 00278 ), 00279 __METHOD__ 00280 ); 00281 00282 if ( $res->numRows() > 0 ) { 00283 $titles = array(); 00284 foreach ( $res as $row ) { 00285 $title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title ); 00286 00287 if ( $this->checkTitle( $title, $row->wl_namespace, $row->wl_title ) 00288 && !$title->isTalkPage() 00289 ) { 00290 $titles[] = $title; 00291 } 00292 } 00293 $res->free(); 00294 00295 GenderCache::singleton()->doTitlesArray( $titles ); 00296 00297 foreach ( $titles as $title ) { 00298 $list[] = $title->getPrefixedText(); 00299 } 00300 } 00301 00302 $this->cleanupWatchlist(); 00303 00304 return $list; 00305 } 00306 00313 private function getWatchlistInfo() { 00314 $titles = array(); 00315 $dbr = wfGetDB( DB_MASTER ); 00316 00317 $res = $dbr->select( 00318 array( 'watchlist' ), 00319 array( 'wl_namespace', 'wl_title' ), 00320 array( 'wl_user' => $this->getUser()->getId() ), 00321 __METHOD__, 00322 array( 'ORDER BY' => array( 'wl_namespace', 'wl_title' ) ) 00323 ); 00324 00325 $lb = new LinkBatch(); 00326 00327 foreach ( $res as $row ) { 00328 $lb->add( $row->wl_namespace, $row->wl_title ); 00329 if ( !MWNamespace::isTalk( $row->wl_namespace ) ) { 00330 $titles[$row->wl_namespace][$row->wl_title] = 1; 00331 } 00332 } 00333 00334 $lb->execute(); 00335 00336 return $titles; 00337 } 00338 00347 private function checkTitle( $title, $namespace, $dbKey ) { 00348 if ( $title 00349 && ( $title->isExternal() 00350 || $title->getNamespace() < 0 00351 ) 00352 ) { 00353 $title = false; // unrecoverable 00354 } 00355 00356 if ( !$title 00357 || $title->getNamespace() != $namespace 00358 || $title->getDBkey() != $dbKey 00359 ) { 00360 $this->badItems[] = array( $title, $namespace, $dbKey ); 00361 } 00362 00363 return (bool)$title; 00364 } 00365 00369 private function cleanupWatchlist() { 00370 if ( !count( $this->badItems ) ) { 00371 return; //nothing to do 00372 } 00373 00374 $dbw = wfGetDB( DB_MASTER ); 00375 $user = $this->getUser(); 00376 00377 foreach ( $this->badItems as $row ) { 00378 list( $title, $namespace, $dbKey ) = $row; 00379 $action = $title ? 'cleaning up' : 'deleting'; 00380 wfDebug( "User {$user->getName()} has broken watchlist item ns($namespace):$dbKey, $action.\n" ); 00381 00382 $dbw->delete( 'watchlist', 00383 array( 00384 'wl_user' => $user->getId(), 00385 'wl_namespace' => $namespace, 00386 'wl_title' => $dbKey, 00387 ), 00388 __METHOD__ 00389 ); 00390 00391 // Can't just do an UPDATE instead of DELETE/INSERT due to unique index 00392 if ( $title ) { 00393 $user->addWatch( $title ); 00394 } 00395 } 00396 } 00397 00401 private function clearWatchlist() { 00402 $dbw = wfGetDB( DB_MASTER ); 00403 $dbw->delete( 00404 'watchlist', 00405 array( 'wl_user' => $this->getUser()->getId() ), 00406 __METHOD__ 00407 ); 00408 } 00409 00418 private function watchTitles( $titles ) { 00419 $dbw = wfGetDB( DB_MASTER ); 00420 $rows = array(); 00421 00422 foreach ( $titles as $title ) { 00423 if ( !$title instanceof Title ) { 00424 $title = Title::newFromText( $title ); 00425 } 00426 00427 if ( $title instanceof Title ) { 00428 $rows[] = array( 00429 'wl_user' => $this->getUser()->getId(), 00430 'wl_namespace' => MWNamespace::getSubject( $title->getNamespace() ), 00431 'wl_title' => $title->getDBkey(), 00432 'wl_notificationtimestamp' => null, 00433 ); 00434 $rows[] = array( 00435 'wl_user' => $this->getUser()->getId(), 00436 'wl_namespace' => MWNamespace::getTalk( $title->getNamespace() ), 00437 'wl_title' => $title->getDBkey(), 00438 'wl_notificationtimestamp' => null, 00439 ); 00440 } 00441 } 00442 00443 $dbw->insert( 'watchlist', $rows, __METHOD__, 'IGNORE' ); 00444 } 00445 00454 private function unwatchTitles( $titles ) { 00455 $dbw = wfGetDB( DB_MASTER ); 00456 00457 foreach ( $titles as $title ) { 00458 if ( !$title instanceof Title ) { 00459 $title = Title::newFromText( $title ); 00460 } 00461 00462 if ( $title instanceof Title ) { 00463 $dbw->delete( 00464 'watchlist', 00465 array( 00466 'wl_user' => $this->getUser()->getId(), 00467 'wl_namespace' => MWNamespace::getSubject( $title->getNamespace() ), 00468 'wl_title' => $title->getDBkey(), 00469 ), 00470 __METHOD__ 00471 ); 00472 00473 $dbw->delete( 00474 'watchlist', 00475 array( 00476 'wl_user' => $this->getUser()->getId(), 00477 'wl_namespace' => MWNamespace::getTalk( $title->getNamespace() ), 00478 'wl_title' => $title->getDBkey(), 00479 ), 00480 __METHOD__ 00481 ); 00482 00483 $page = WikiPage::factory( $title ); 00484 wfRunHooks( 'UnwatchArticleComplete', array( $this->getUser(), &$page ) ); 00485 } 00486 } 00487 } 00488 00489 public function submitNormal( $data ) { 00490 $removed = array(); 00491 00492 foreach ( $data as $titles ) { 00493 $this->unwatchTitles( $titles ); 00494 $removed = array_merge( $removed, $titles ); 00495 } 00496 00497 if ( count( $removed ) > 0 ) { 00498 $this->successMessage = $this->msg( 'watchlistedit-normal-done' 00499 )->numParams( count( $removed ) )->parse(); 00500 $this->showTitles( $removed, $this->successMessage ); 00501 00502 return true; 00503 } else { 00504 return false; 00505 } 00506 } 00507 00513 protected function getNormalForm() { 00514 global $wgContLang; 00515 00516 $fields = array(); 00517 $count = 0; 00518 00519 foreach ( $this->getWatchlistInfo() as $namespace => $pages ) { 00520 if ( $namespace >= 0 ) { 00521 $fields['TitlesNs' . $namespace] = array( 00522 'class' => 'EditWatchlistCheckboxSeriesField', 00523 'options' => array(), 00524 'section' => "ns$namespace", 00525 ); 00526 } 00527 00528 foreach ( array_keys( $pages ) as $dbkey ) { 00529 $title = Title::makeTitleSafe( $namespace, $dbkey ); 00530 00531 if ( $this->checkTitle( $title, $namespace, $dbkey ) ) { 00532 $text = $this->buildRemoveLine( $title ); 00533 $fields['TitlesNs' . $namespace]['options'][$text] = $title->getPrefixedText(); 00534 $count++; 00535 } 00536 } 00537 } 00538 $this->cleanupWatchlist(); 00539 00540 if ( count( $fields ) > 1 && $count > 30 ) { 00541 $this->toc = Linker::tocIndent(); 00542 $tocLength = 0; 00543 00544 foreach ( $fields as $data ) { 00545 # strip out the 'ns' prefix from the section name: 00546 $ns = substr( $data['section'], 2 ); 00547 00548 $nsText = ( $ns == NS_MAIN ) 00549 ? $this->msg( 'blanknamespace' )->escaped() 00550 : htmlspecialchars( $wgContLang->getFormattedNsText( $ns ) ); 00551 $this->toc .= Linker::tocLine( "editwatchlist-{$data['section']}", $nsText, 00552 $this->getLanguage()->formatNum( ++$tocLength ), 1 ) . Linker::tocLineEnd(); 00553 } 00554 00555 $this->toc = Linker::tocList( $this->toc ); 00556 } else { 00557 $this->toc = false; 00558 } 00559 00560 $context = new DerivativeContext( $this->getContext() ); 00561 $context->setTitle( $this->getPageTitle() ); // Remove subpage 00562 $form = new EditWatchlistNormalHTMLForm( $fields, $context ); 00563 $form->setSubmitTextMsg( 'watchlistedit-normal-submit' ); 00564 # Used message keys: 'accesskey-watchlistedit-normal-submit', 'tooltip-watchlistedit-normal-submit' 00565 $form->setSubmitTooltip( 'watchlistedit-normal-submit' ); 00566 $form->setWrapperLegendMsg( 'watchlistedit-normal-legend' ); 00567 $form->addHeaderText( $this->msg( 'watchlistedit-normal-explain' )->parse() ); 00568 $form->setSubmitCallback( array( $this, 'submitNormal' ) ); 00569 00570 return $form; 00571 } 00572 00579 private function buildRemoveLine( $title ) { 00580 $link = Linker::link( $title ); 00581 00582 if ( $title->isRedirect() ) { 00583 // Linker already makes class mw-redirect, so this is redundant 00584 $link = '<span class="watchlistredir">' . $link . '</span>'; 00585 } 00586 00587 $tools[] = Linker::link( $title->getTalkPage(), $this->msg( 'talkpagelinktext' )->escaped() ); 00588 00589 if ( $title->exists() ) { 00590 $tools[] = Linker::linkKnown( 00591 $title, 00592 $this->msg( 'history_short' )->escaped(), 00593 array(), 00594 array( 'action' => 'history' ) 00595 ); 00596 } 00597 00598 if ( $title->getNamespace() == NS_USER && !$title->isSubpage() ) { 00599 $tools[] = Linker::linkKnown( 00600 SpecialPage::getTitleFor( 'Contributions', $title->getText() ), 00601 $this->msg( 'contributions' )->escaped() 00602 ); 00603 } 00604 00605 wfRunHooks( 'WatchlistEditorBuildRemoveLine', array( &$tools, $title, $title->isRedirect(), $this->getSkin() ) ); 00606 00607 return $link . " (" . $this->getLanguage()->pipeList( $tools ) . ")"; 00608 } 00609 00615 protected function getRawForm() { 00616 $titles = implode( $this->getWatchlist(), "\n" ); 00617 $fields = array( 00618 'Titles' => array( 00619 'type' => 'textarea', 00620 'label-message' => 'watchlistedit-raw-titles', 00621 'default' => $titles, 00622 ), 00623 ); 00624 $context = new DerivativeContext( $this->getContext() ); 00625 $context->setTitle( $this->getPageTitle( 'raw' ) ); // Reset subpage 00626 $form = new HTMLForm( $fields, $context ); 00627 $form->setSubmitTextMsg( 'watchlistedit-raw-submit' ); 00628 # Used message keys: 'accesskey-watchlistedit-raw-submit', 'tooltip-watchlistedit-raw-submit' 00629 $form->setSubmitTooltip( 'watchlistedit-raw-submit' ); 00630 $form->setWrapperLegendMsg( 'watchlistedit-raw-legend' ); 00631 $form->addHeaderText( $this->msg( 'watchlistedit-raw-explain' )->parse() ); 00632 $form->setSubmitCallback( array( $this, 'submitRaw' ) ); 00633 00634 return $form; 00635 } 00636 00642 protected function getClearForm() { 00643 $context = new DerivativeContext( $this->getContext() ); 00644 $context->setTitle( $this->getPageTitle( 'clear' ) ); // Reset subpage 00645 $form = new HTMLForm( array(), $context ); 00646 $form->setSubmitTextMsg( 'watchlistedit-clear-submit' ); 00647 # Used message keys: 'accesskey-watchlistedit-clear-submit', 'tooltip-watchlistedit-clear-submit' 00648 $form->setSubmitTooltip( 'watchlistedit-clear-submit' ); 00649 $form->setWrapperLegendMsg( 'watchlistedit-clear-legend' ); 00650 $form->addHeaderText( $this->msg( 'watchlistedit-clear-explain' )->parse() ); 00651 $form->setSubmitCallback( array( $this, 'submitClear' ) ); 00652 00653 return $form; 00654 } 00655 00664 public static function getMode( $request, $par ) { 00665 $mode = strtolower( $request->getVal( 'action', $par ) ); 00666 00667 switch ( $mode ) { 00668 case 'clear': 00669 case self::EDIT_CLEAR: 00670 return self::EDIT_CLEAR; 00671 case 'raw': 00672 case self::EDIT_RAW: 00673 return self::EDIT_RAW; 00674 case 'edit': 00675 case self::EDIT_NORMAL: 00676 return self::EDIT_NORMAL; 00677 default: 00678 return false; 00679 } 00680 } 00681 00689 public static function buildTools( $unused ) { 00690 global $wgLang; 00691 00692 $tools = array(); 00693 $modes = array( 00694 'view' => array( 'Watchlist', false ), 00695 'edit' => array( 'EditWatchlist', false ), 00696 'raw' => array( 'EditWatchlist', 'raw' ), 00697 'clear' => array( 'EditWatchlist', 'clear' ), 00698 ); 00699 00700 foreach ( $modes as $mode => $arr ) { 00701 // can use messages 'watchlisttools-view', 'watchlisttools-edit', 'watchlisttools-raw' 00702 $tools[] = Linker::linkKnown( 00703 SpecialPage::getTitleFor( $arr[0], $arr[1] ), 00704 wfMessage( "watchlisttools-{$mode}" )->escaped() 00705 ); 00706 } 00707 00708 return Html::rawElement( 00709 'span', 00710 array( 'class' => 'mw-watchlist-toollinks' ), 00711 wfMessage( 'parentheses', $wgLang->pipeList( $tools ) )->text() 00712 ); 00713 } 00714 } 00715 00716 # B/C since 1.18 00717 class WatchlistEditor extends SpecialEditWatchlist { 00718 } 00719 00723 class EditWatchlistNormalHTMLForm extends HTMLForm { 00724 public function getLegend( $namespace ) { 00725 $namespace = substr( $namespace, 2 ); 00726 00727 return $namespace == NS_MAIN 00728 ? $this->msg( 'blanknamespace' )->escaped() 00729 : htmlspecialchars( $this->getContext()->getLanguage()->getFormattedNsText( $namespace ) ); 00730 } 00731 00732 public function getBody() { 00733 return $this->displaySection( $this->mFieldTree, '', 'editwatchlist-' ); 00734 } 00735 } 00736 00737 class EditWatchlistCheckboxSeriesField extends HTMLMultiSelectField { 00749 function validate( $value, $alldata ) { 00750 // Need to call into grandparent to be a good citizen. :) 00751 return HTMLFormField::validate( $value, $alldata ); 00752 } 00753 }