MediaWiki
REL1_22
|
00001 <?php 00037 class SpecialEditWatchlist extends UnlistedSpecialPage { 00041 const EDIT_CLEAR = 1; 00042 const EDIT_RAW = 2; 00043 const EDIT_NORMAL = 3; 00044 00045 protected $successMessage; 00046 00047 protected $toc; 00048 00049 private $badItems = array(); 00050 00051 public function __construct() { 00052 parent::__construct( 'EditWatchlist', 'editmywatchlist' ); 00053 } 00054 00060 public function execute( $mode ) { 00061 $this->setHeaders(); 00062 00063 $out = $this->getOutput(); 00064 00065 # Anons don't get a watchlist 00066 if ( $this->getUser()->isAnon() ) { 00067 $out->setPageTitle( $this->msg( 'watchnologin' ) ); 00068 $llink = Linker::linkKnown( 00069 SpecialPage::getTitleFor( 'Userlogin' ), 00070 $this->msg( 'loginreqlink' )->escaped(), 00071 array(), 00072 array( 'returnto' => $this->getTitle()->getPrefixedText() ) 00073 ); 00074 $out->addHTML( $this->msg( 'watchlistanontext' )->rawParams( $llink )->parse() ); 00075 00076 return; 00077 } 00078 00079 $this->checkPermissions(); 00080 $this->checkReadOnly(); 00081 00082 $this->outputHeader(); 00083 00084 $out->addSubtitle( $this->msg( 'watchlistfor2', $this->getUser()->getName() ) 00085 ->rawParams( SpecialEditWatchlist::buildTools( null ) ) ); 00086 00087 # B/C: $mode used to be waaay down the parameter list, and the first parameter 00088 # was $wgUser 00089 if ( $mode instanceof User ) { 00090 $args = func_get_args(); 00091 if ( count( $args ) >= 4 ) { 00092 $mode = $args[3]; 00093 } 00094 } 00095 $mode = self::getMode( $this->getRequest(), $mode ); 00096 00097 switch ( $mode ) { 00098 case self::EDIT_CLEAR: 00099 // The "Clear" link scared people too much. 00100 // Pass on to the raw editor, from which it's very easy to clear. 00101 00102 case self::EDIT_RAW: 00103 $out->setPageTitle( $this->msg( 'watchlistedit-raw-title' ) ); 00104 $form = $this->getRawForm(); 00105 if ( $form->show() ) { 00106 $out->addHTML( $this->successMessage ); 00107 $out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) ); 00108 } 00109 break; 00110 00111 case self::EDIT_NORMAL: 00112 default: 00113 $out->setPageTitle( $this->msg( 'watchlistedit-normal-title' ) ); 00114 $form = $this->getNormalForm(); 00115 if ( $form->show() ) { 00116 $out->addHTML( $this->successMessage ); 00117 $out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) ); 00118 } elseif ( $this->toc !== false ) { 00119 $out->prependHTML( $this->toc ); 00120 } 00121 break; 00122 } 00123 } 00124 00132 private function extractTitles( $list ) { 00133 $list = explode( "\n", trim( $list ) ); 00134 if ( !is_array( $list ) ) { 00135 return array(); 00136 } 00137 00138 $titles = array(); 00139 00140 foreach ( $list as $text ) { 00141 $text = trim( $text ); 00142 if ( strlen( $text ) > 0 ) { 00143 $title = Title::newFromText( $text ); 00144 if ( $title instanceof Title && $title->isWatchable() ) { 00145 $titles[] = $title; 00146 } 00147 } 00148 } 00149 00150 GenderCache::singleton()->doTitlesArray( $titles ); 00151 00152 $list = array(); 00154 foreach ( $titles as $title ) { 00155 $list[] = $title->getPrefixedText(); 00156 } 00157 00158 return array_unique( $list ); 00159 } 00160 00161 public function submitRaw( $data ) { 00162 $wanted = $this->extractTitles( $data['Titles'] ); 00163 $current = $this->getWatchlist(); 00164 00165 if ( count( $wanted ) > 0 ) { 00166 $toWatch = array_diff( $wanted, $current ); 00167 $toUnwatch = array_diff( $current, $wanted ); 00168 $this->watchTitles( $toWatch ); 00169 $this->unwatchTitles( $toUnwatch ); 00170 $this->getUser()->invalidateCache(); 00171 00172 if ( count( $toWatch ) > 0 || count( $toUnwatch ) > 0 ) { 00173 $this->successMessage = $this->msg( 'watchlistedit-raw-done' )->parse(); 00174 } else { 00175 return false; 00176 } 00177 00178 if ( count( $toWatch ) > 0 ) { 00179 $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-added' 00180 )->numParams( count( $toWatch ) )->parse(); 00181 $this->showTitles( $toWatch, $this->successMessage ); 00182 } 00183 00184 if ( count( $toUnwatch ) > 0 ) { 00185 $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-removed' 00186 )->numParams( count( $toUnwatch ) )->parse(); 00187 $this->showTitles( $toUnwatch, $this->successMessage ); 00188 } 00189 } else { 00190 $this->clearWatchlist(); 00191 $this->getUser()->invalidateCache(); 00192 00193 if ( count( $current ) > 0 ) { 00194 $this->successMessage = $this->msg( 'watchlistedit-raw-done' )->parse(); 00195 } else { 00196 return false; 00197 } 00198 00199 $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-removed' ) 00200 ->numParams( count( $current ) )->parse(); 00201 $this->showTitles( $current, $this->successMessage ); 00202 } 00203 00204 return true; 00205 } 00206 00216 private function showTitles( $titles, &$output ) { 00217 $talk = $this->msg( 'talkpagelinktext' )->escaped(); 00218 // Do a batch existence check 00219 $batch = new LinkBatch(); 00220 foreach ( $titles as $title ) { 00221 if ( !$title instanceof Title ) { 00222 $title = Title::newFromText( $title ); 00223 } 00224 00225 if ( $title instanceof Title ) { 00226 $batch->addObj( $title ); 00227 $batch->addObj( $title->getTalkPage() ); 00228 } 00229 } 00230 00231 $batch->execute(); 00232 00233 // Print out the list 00234 $output .= "<ul>\n"; 00235 00236 foreach ( $titles as $title ) { 00237 if ( !$title instanceof Title ) { 00238 $title = Title::newFromText( $title ); 00239 } 00240 00241 if ( $title instanceof Title ) { 00242 $output .= "<li>" 00243 . Linker::link( $title ) 00244 . ' (' . Linker::link( $title->getTalkPage(), $talk ) 00245 . ")</li>\n"; 00246 } 00247 } 00248 00249 $output .= "</ul>\n"; 00250 } 00251 00258 private function getWatchlist() { 00259 $list = array(); 00260 $dbr = wfGetDB( DB_MASTER ); 00261 00262 $res = $dbr->select( 00263 'watchlist', 00264 array( 00265 'wl_namespace', 'wl_title' 00266 ), array( 00267 'wl_user' => $this->getUser()->getId(), 00268 ), 00269 __METHOD__ 00270 ); 00271 00272 if ( $res->numRows() > 0 ) { 00273 $titles = array(); 00274 foreach ( $res as $row ) { 00275 $title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title ); 00276 00277 if ( $this->checkTitle( $title, $row->wl_namespace, $row->wl_title ) 00278 && !$title->isTalkPage() 00279 ) { 00280 $titles[] = $title; 00281 } 00282 } 00283 $res->free(); 00284 00285 GenderCache::singleton()->doTitlesArray( $titles ); 00286 00287 foreach ( $titles as $title ) { 00288 $list[] = $title->getPrefixedText(); 00289 } 00290 } 00291 00292 $this->cleanupWatchlist(); 00293 00294 return $list; 00295 } 00296 00303 private function getWatchlistInfo() { 00304 $titles = array(); 00305 $dbr = wfGetDB( DB_MASTER ); 00306 00307 $res = $dbr->select( 00308 array( 'watchlist' ), 00309 array( 'wl_namespace', 'wl_title' ), 00310 array( 'wl_user' => $this->getUser()->getId() ), 00311 __METHOD__, 00312 array( 'ORDER BY' => array( 'wl_namespace', 'wl_title' ) ) 00313 ); 00314 00315 $lb = new LinkBatch(); 00316 00317 foreach ( $res as $row ) { 00318 $lb->add( $row->wl_namespace, $row->wl_title ); 00319 if ( !MWNamespace::isTalk( $row->wl_namespace ) ) { 00320 $titles[$row->wl_namespace][$row->wl_title] = 1; 00321 } 00322 } 00323 00324 $lb->execute(); 00325 00326 return $titles; 00327 } 00328 00337 private function checkTitle( $title, $namespace, $dbKey ) { 00338 if ( $title 00339 && ( $title->isExternal() 00340 || $title->getNamespace() < 0 00341 ) 00342 ) { 00343 $title = false; // unrecoverable 00344 } 00345 00346 if ( !$title 00347 || $title->getNamespace() != $namespace 00348 || $title->getDBkey() != $dbKey 00349 ) { 00350 $this->badItems[] = array( $title, $namespace, $dbKey ); 00351 } 00352 00353 return (bool)$title; 00354 } 00355 00359 private function cleanupWatchlist() { 00360 if ( !count( $this->badItems ) ) { 00361 return; //nothing to do 00362 } 00363 00364 $dbw = wfGetDB( DB_MASTER ); 00365 $user = $this->getUser(); 00366 00367 foreach ( $this->badItems as $row ) { 00368 list( $title, $namespace, $dbKey ) = $row; 00369 $action = $title ? 'cleaning up' : 'deleting'; 00370 wfDebug( "User {$user->getName()} has broken watchlist item ns($namespace):$dbKey, $action.\n" ); 00371 00372 $dbw->delete( 'watchlist', 00373 array( 00374 'wl_user' => $user->getId(), 00375 'wl_namespace' => $namespace, 00376 'wl_title' => $dbKey, 00377 ), 00378 __METHOD__ 00379 ); 00380 00381 // Can't just do an UPDATE instead of DELETE/INSERT due to unique index 00382 if ( $title ) { 00383 $user->addWatch( $title ); 00384 } 00385 } 00386 } 00387 00391 private function clearWatchlist() { 00392 $dbw = wfGetDB( DB_MASTER ); 00393 $dbw->delete( 00394 'watchlist', 00395 array( 'wl_user' => $this->getUser()->getId() ), 00396 __METHOD__ 00397 ); 00398 } 00399 00408 private function watchTitles( $titles ) { 00409 $dbw = wfGetDB( DB_MASTER ); 00410 $rows = array(); 00411 00412 foreach ( $titles as $title ) { 00413 if ( !$title instanceof Title ) { 00414 $title = Title::newFromText( $title ); 00415 } 00416 00417 if ( $title instanceof Title ) { 00418 $rows[] = array( 00419 'wl_user' => $this->getUser()->getId(), 00420 'wl_namespace' => MWNamespace::getSubject( $title->getNamespace() ), 00421 'wl_title' => $title->getDBkey(), 00422 'wl_notificationtimestamp' => null, 00423 ); 00424 $rows[] = array( 00425 'wl_user' => $this->getUser()->getId(), 00426 'wl_namespace' => MWNamespace::getTalk( $title->getNamespace() ), 00427 'wl_title' => $title->getDBkey(), 00428 'wl_notificationtimestamp' => null, 00429 ); 00430 } 00431 } 00432 00433 $dbw->insert( 'watchlist', $rows, __METHOD__, 'IGNORE' ); 00434 } 00435 00444 private function unwatchTitles( $titles ) { 00445 $dbw = wfGetDB( DB_MASTER ); 00446 00447 foreach ( $titles as $title ) { 00448 if ( !$title instanceof Title ) { 00449 $title = Title::newFromText( $title ); 00450 } 00451 00452 if ( $title instanceof Title ) { 00453 $dbw->delete( 00454 'watchlist', 00455 array( 00456 'wl_user' => $this->getUser()->getId(), 00457 'wl_namespace' => MWNamespace::getSubject( $title->getNamespace() ), 00458 'wl_title' => $title->getDBkey(), 00459 ), 00460 __METHOD__ 00461 ); 00462 00463 $dbw->delete( 00464 'watchlist', 00465 array( 00466 'wl_user' => $this->getUser()->getId(), 00467 'wl_namespace' => MWNamespace::getTalk( $title->getNamespace() ), 00468 'wl_title' => $title->getDBkey(), 00469 ), 00470 __METHOD__ 00471 ); 00472 00473 $page = WikiPage::factory( $title ); 00474 wfRunHooks( 'UnwatchArticleComplete', array( $this->getUser(), &$page ) ); 00475 } 00476 } 00477 } 00478 00479 public function submitNormal( $data ) { 00480 $removed = array(); 00481 00482 foreach ( $data as $titles ) { 00483 $this->unwatchTitles( $titles ); 00484 $removed = array_merge( $removed, $titles ); 00485 } 00486 00487 if ( count( $removed ) > 0 ) { 00488 $this->successMessage = $this->msg( 'watchlistedit-normal-done' 00489 )->numParams( count( $removed ) )->parse(); 00490 $this->showTitles( $removed, $this->successMessage ); 00491 00492 return true; 00493 } else { 00494 return false; 00495 } 00496 } 00497 00503 protected function getNormalForm() { 00504 global $wgContLang; 00505 00506 $fields = array(); 00507 $count = 0; 00508 00509 foreach ( $this->getWatchlistInfo() as $namespace => $pages ) { 00510 if ( $namespace >= 0 ) { 00511 $fields['TitlesNs' . $namespace] = array( 00512 'class' => 'EditWatchlistCheckboxSeriesField', 00513 'options' => array(), 00514 'section' => "ns$namespace", 00515 ); 00516 } 00517 00518 foreach ( array_keys( $pages ) as $dbkey ) { 00519 $title = Title::makeTitleSafe( $namespace, $dbkey ); 00520 00521 if ( $this->checkTitle( $title, $namespace, $dbkey ) ) { 00522 $text = $this->buildRemoveLine( $title ); 00523 $fields['TitlesNs' . $namespace]['options'][$text] = $title->getPrefixedText(); 00524 $count++; 00525 } 00526 } 00527 } 00528 $this->cleanupWatchlist(); 00529 00530 if ( count( $fields ) > 1 && $count > 30 ) { 00531 $this->toc = Linker::tocIndent(); 00532 $tocLength = 0; 00533 00534 foreach ( $fields as $data ) { 00535 # strip out the 'ns' prefix from the section name: 00536 $ns = substr( $data['section'], 2 ); 00537 00538 $nsText = ( $ns == NS_MAIN ) 00539 ? $this->msg( 'blanknamespace' )->escaped() 00540 : htmlspecialchars( $wgContLang->getFormattedNsText( $ns ) ); 00541 $this->toc .= Linker::tocLine( "editwatchlist-{$data['section']}", $nsText, 00542 $this->getLanguage()->formatNum( ++$tocLength ), 1 ) . Linker::tocLineEnd(); 00543 } 00544 00545 $this->toc = Linker::tocList( $this->toc ); 00546 } else { 00547 $this->toc = false; 00548 } 00549 00550 $context = new DerivativeContext( $this->getContext() ); 00551 $context->setTitle( $this->getTitle() ); // Remove subpage 00552 $form = new EditWatchlistNormalHTMLForm( $fields, $context ); 00553 $form->setSubmitTextMsg( 'watchlistedit-normal-submit' ); 00554 # Used message keys: 'accesskey-watchlistedit-normal-submit', 'tooltip-watchlistedit-normal-submit' 00555 $form->setSubmitTooltip( 'watchlistedit-normal-submit' ); 00556 $form->setWrapperLegendMsg( 'watchlistedit-normal-legend' ); 00557 $form->addHeaderText( $this->msg( 'watchlistedit-normal-explain' )->parse() ); 00558 $form->setSubmitCallback( array( $this, 'submitNormal' ) ); 00559 00560 return $form; 00561 } 00562 00569 private function buildRemoveLine( $title ) { 00570 $link = Linker::link( $title ); 00571 00572 if ( $title->isRedirect() ) { 00573 // Linker already makes class mw-redirect, so this is redundant 00574 $link = '<span class="watchlistredir">' . $link . '</span>'; 00575 } 00576 00577 $tools[] = Linker::link( $title->getTalkPage(), $this->msg( 'talkpagelinktext' )->escaped() ); 00578 00579 if ( $title->exists() ) { 00580 $tools[] = Linker::linkKnown( 00581 $title, 00582 $this->msg( 'history_short' )->escaped(), 00583 array(), 00584 array( 'action' => 'history' ) 00585 ); 00586 } 00587 00588 if ( $title->getNamespace() == NS_USER && !$title->isSubpage() ) { 00589 $tools[] = Linker::linkKnown( 00590 SpecialPage::getTitleFor( 'Contributions', $title->getText() ), 00591 $this->msg( 'contributions' )->escaped() 00592 ); 00593 } 00594 00595 wfRunHooks( 'WatchlistEditorBuildRemoveLine', array( &$tools, $title, $title->isRedirect(), $this->getSkin() ) ); 00596 00597 return $link . " (" . $this->getLanguage()->pipeList( $tools ) . ")"; 00598 } 00599 00605 protected function getRawForm() { 00606 $titles = implode( $this->getWatchlist(), "\n" ); 00607 $fields = array( 00608 'Titles' => array( 00609 'type' => 'textarea', 00610 'label-message' => 'watchlistedit-raw-titles', 00611 'default' => $titles, 00612 ), 00613 ); 00614 $context = new DerivativeContext( $this->getContext() ); 00615 $context->setTitle( $this->getTitle( 'raw' ) ); // Reset subpage 00616 $form = new HTMLForm( $fields, $context ); 00617 $form->setSubmitTextMsg( 'watchlistedit-raw-submit' ); 00618 # Used message keys: 'accesskey-watchlistedit-raw-submit', 'tooltip-watchlistedit-raw-submit' 00619 $form->setSubmitTooltip( 'watchlistedit-raw-submit' ); 00620 $form->setWrapperLegendMsg( 'watchlistedit-raw-legend' ); 00621 $form->addHeaderText( $this->msg( 'watchlistedit-raw-explain' )->parse() ); 00622 $form->setSubmitCallback( array( $this, 'submitRaw' ) ); 00623 00624 return $form; 00625 } 00626 00635 public static function getMode( $request, $par ) { 00636 $mode = strtolower( $request->getVal( 'action', $par ) ); 00637 00638 switch ( $mode ) { 00639 case 'clear': 00640 case self::EDIT_CLEAR: 00641 return self::EDIT_CLEAR; 00642 case 'raw': 00643 case self::EDIT_RAW: 00644 return self::EDIT_RAW; 00645 case 'edit': 00646 case self::EDIT_NORMAL: 00647 return self::EDIT_NORMAL; 00648 default: 00649 return false; 00650 } 00651 } 00652 00660 public static function buildTools( $unused ) { 00661 global $wgLang; 00662 00663 $tools = array(); 00664 $modes = array( 00665 'view' => array( 'Watchlist', false ), 00666 'edit' => array( 'EditWatchlist', false ), 00667 'raw' => array( 'EditWatchlist', 'raw' ), 00668 ); 00669 00670 foreach ( $modes as $mode => $arr ) { 00671 // can use messages 'watchlisttools-view', 'watchlisttools-edit', 'watchlisttools-raw' 00672 $tools[] = Linker::linkKnown( 00673 SpecialPage::getTitleFor( $arr[0], $arr[1] ), 00674 wfMessage( "watchlisttools-{$mode}" )->escaped() 00675 ); 00676 } 00677 00678 return Html::rawElement( 00679 'span', 00680 array( 'class' => 'mw-watchlist-toollinks' ), 00681 wfMessage( 'parentheses', $wgLang->pipeList( $tools ) )->text() 00682 ); 00683 } 00684 } 00685 00686 # B/C since 1.18 00687 class WatchlistEditor extends SpecialEditWatchlist { 00688 } 00689 00693 class EditWatchlistNormalHTMLForm extends HTMLForm { 00694 public function getLegend( $namespace ) { 00695 $namespace = substr( $namespace, 2 ); 00696 00697 return $namespace == NS_MAIN 00698 ? $this->msg( 'blanknamespace' )->escaped() 00699 : htmlspecialchars( $this->getContext()->getLanguage()->getFormattedNsText( $namespace ) ); 00700 } 00701 00702 public function getBody() { 00703 return $this->displaySection( $this->mFieldTree, '', 'editwatchlist-' ); 00704 } 00705 } 00706 00707 class EditWatchlistCheckboxSeriesField extends HTMLMultiSelectField { 00719 function validate( $value, $alldata ) { 00720 // Need to call into grandparent to be a good citizen. :) 00721 return HTMLFormField::validate( $value, $alldata ); 00722 } 00723 }