MediaWiki
REL1_20
|
00001 <?php 00037 class SpecialEditWatchlist extends UnlistedSpecialPage { 00038 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' ); 00054 } 00055 00061 public function execute( $mode ) { 00062 $this->setHeaders(); 00063 00064 $out = $this->getOutput(); 00065 00066 # Anons don't get a watchlist 00067 if( $this->getUser()->isAnon() ) { 00068 $out->setPageTitle( $this->msg( 'watchnologin' ) ); 00069 $llink = Linker::linkKnown( 00070 SpecialPage::getTitleFor( 'Userlogin' ), 00071 $this->msg( 'loginreqlink' )->escaped(), 00072 array(), 00073 array( 'returnto' => $this->getTitle()->getPrefixedText() ) 00074 ); 00075 $out->addHTML( $this->msg( 'watchlistanontext' )->rawParams( $llink )->parse() ); 00076 return; 00077 } 00078 00079 $this->checkPermissions(); 00080 00081 $this->outputHeader(); 00082 00083 $out->addSubtitle( $this->msg( 'watchlistfor2', $this->getUser()->getName() 00084 )->rawParams( SpecialEditWatchlist::buildTools( null ) ) ); 00085 00086 # B/C: $mode used to be waaay down the parameter list, and the first parameter 00087 # was $wgUser 00088 if( $mode instanceof User ){ 00089 $args = func_get_args(); 00090 if( count( $args >= 4 ) ){ 00091 $mode = $args[3]; 00092 } 00093 } 00094 $mode = self::getMode( $this->getRequest(), $mode ); 00095 00096 switch( $mode ) { 00097 case self::EDIT_CLEAR: 00098 // The "Clear" link scared people too much. 00099 // Pass on to the raw editor, from which it's very easy to clear. 00100 00101 case self::EDIT_RAW: 00102 $out->setPageTitle( $this->msg( 'watchlistedit-raw-title' ) ); 00103 $form = $this->getRawForm(); 00104 if( $form->show() ){ 00105 $out->addHTML( $this->successMessage ); 00106 $out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) ); 00107 } 00108 break; 00109 00110 case self::EDIT_NORMAL: 00111 default: 00112 $out->setPageTitle( $this->msg( 'watchlistedit-normal-title' ) ); 00113 $form = $this->getNormalForm(); 00114 if( $form->show() ){ 00115 $out->addHTML( $this->successMessage ); 00116 $out->addReturnTo( SpecialPage::getTitleFor( 'Watchlist' ) ); 00117 } elseif ( $this->toc !== false ) { 00118 $out->prependHTML( $this->toc ); 00119 } 00120 break; 00121 } 00122 } 00123 00131 private function extractTitles( $list ) { 00132 $list = explode( "\n", trim( $list ) ); 00133 if( !is_array( $list ) ) { 00134 return array(); 00135 } 00136 $titles = array(); 00137 foreach( $list as $text ) { 00138 $text = trim( $text ); 00139 if( strlen( $text ) > 0 ) { 00140 $title = Title::newFromText( $text ); 00141 if( $title instanceof Title && $title->isWatchable() ) { 00142 $titles[] = $title; 00143 } 00144 } 00145 } 00146 00147 GenderCache::singleton()->doTitlesArray( $titles ); 00148 00149 $list = array(); 00150 foreach( $titles as $title ) { 00151 $list[] = $title->getPrefixedText(); 00152 } 00153 return array_unique( $list ); 00154 } 00155 00156 public function submitRaw( $data ){ 00157 $wanted = $this->extractTitles( $data['Titles'] ); 00158 $current = $this->getWatchlist(); 00159 00160 if( count( $wanted ) > 0 ) { 00161 $toWatch = array_diff( $wanted, $current ); 00162 $toUnwatch = array_diff( $current, $wanted ); 00163 $this->watchTitles( $toWatch ); 00164 $this->unwatchTitles( $toUnwatch ); 00165 $this->getUser()->invalidateCache(); 00166 00167 if( count( $toWatch ) > 0 || count( $toUnwatch ) > 0 ){ 00168 $this->successMessage = $this->msg( 'watchlistedit-raw-done' )->parse(); 00169 } else { 00170 return false; 00171 } 00172 00173 if( count( $toWatch ) > 0 ) { 00174 $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-added' 00175 )->numParams( count( $toWatch ) )->parse(); 00176 $this->showTitles( $toWatch, $this->successMessage ); 00177 } 00178 00179 if( count( $toUnwatch ) > 0 ) { 00180 $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-removed' 00181 )->numParams( count( $toUnwatch ) )->parse(); 00182 $this->showTitles( $toUnwatch, $this->successMessage ); 00183 } 00184 } else { 00185 $this->clearWatchlist(); 00186 $this->getUser()->invalidateCache(); 00187 00188 if( count( $current ) > 0 ){ 00189 $this->successMessage = $this->msg( 'watchlistedit-raw-done' )->parse(); 00190 } else { 00191 return false; 00192 } 00193 00194 $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-removed' 00195 )->numParams( count( $current ) )->parse(); 00196 $this->showTitles( $current, $this->successMessage ); 00197 } 00198 return true; 00199 } 00200 00210 private function showTitles( $titles, &$output ) { 00211 $talk = $this->msg( 'talkpagelinktext' )->escaped(); 00212 // Do a batch existence check 00213 $batch = new LinkBatch(); 00214 foreach( $titles as $title ) { 00215 if( !$title instanceof Title ) { 00216 $title = Title::newFromText( $title ); 00217 } 00218 if( $title instanceof Title ) { 00219 $batch->addObj( $title ); 00220 $batch->addObj( $title->getTalkPage() ); 00221 } 00222 } 00223 $batch->execute(); 00224 // Print out the list 00225 $output .= "<ul>\n"; 00226 foreach( $titles as $title ) { 00227 if( !$title instanceof Title ) { 00228 $title = Title::newFromText( $title ); 00229 } 00230 if( $title instanceof Title ) { 00231 $output .= "<li>" 00232 . Linker::link( $title ) 00233 . ' (' . Linker::link( $title->getTalkPage(), $talk ) 00234 . ")</li>\n"; 00235 } 00236 } 00237 $output .= "</ul>\n"; 00238 } 00239 00246 private function getWatchlist() { 00247 $list = array(); 00248 $dbr = wfGetDB( DB_MASTER ); 00249 $res = $dbr->select( 00250 'watchlist', 00251 array( 00252 'wl_namespace', 'wl_title' 00253 ), array( 00254 'wl_user' => $this->getUser()->getId(), 00255 ), 00256 __METHOD__ 00257 ); 00258 if( $res->numRows() > 0 ) { 00259 $titles = array(); 00260 foreach ( $res as $row ) { 00261 $title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title ); 00262 if ( $this->checkTitle( $title, $row->wl_namespace, $row->wl_title ) 00263 && !$title->isTalkPage() 00264 ) { 00265 $titles[] = $title; 00266 } 00267 } 00268 $res->free(); 00269 00270 GenderCache::singleton()->doTitlesArray( $titles ); 00271 00272 foreach( $titles as $title ) { 00273 $list[] = $title->getPrefixedText(); 00274 } 00275 } 00276 $this->cleanupWatchlist(); 00277 return $list; 00278 } 00279 00286 private function getWatchlistInfo() { 00287 $titles = array(); 00288 $dbr = wfGetDB( DB_MASTER ); 00289 00290 $res = $dbr->select( 00291 array( 'watchlist' ), 00292 array( 'wl_namespace', 'wl_title' ), 00293 array( 'wl_user' => $this->getUser()->getId() ), 00294 __METHOD__, 00295 array( 'ORDER BY' => array( 'wl_namespace', 'wl_title' ) ) 00296 ); 00297 00298 $lb = new LinkBatch(); 00299 foreach ( $res as $row ) { 00300 $lb->add( $row->wl_namespace, $row->wl_title ); 00301 if ( !MWNamespace::isTalk( $row->wl_namespace ) ) { 00302 $titles[$row->wl_namespace][$row->wl_title] = 1; 00303 } 00304 } 00305 00306 $lb->execute(); 00307 return $titles; 00308 } 00309 00318 private function checkTitle( $title, $namespace, $dbKey ) { 00319 if ( $title 00320 && ( $title->isExternal() 00321 || $title->getNamespace() < 0 00322 ) 00323 ) { 00324 $title = false; // unrecoverable 00325 } 00326 if ( !$title 00327 || $title->getNamespace() != $namespace 00328 || $title->getDBkey() != $dbKey 00329 ) { 00330 $this->badItems[] = array( $title, $namespace, $dbKey ); 00331 } 00332 return (bool)$title; 00333 } 00334 00338 private function cleanupWatchlist() { 00339 if( !count( $this->badItems ) ) { 00340 return; //nothing to do 00341 } 00342 $dbw = wfGetDB( DB_MASTER ); 00343 $user = $this->getUser(); 00344 foreach ( $this->badItems as $row ) { 00345 list( $title, $namespace, $dbKey ) = $row; 00346 wfDebug( "User {$user->getName()} has broken watchlist item ns($namespace):$dbKey, " 00347 . ( $title ? 'cleaning up' : 'deleting' ) . ".\n" 00348 ); 00349 00350 $dbw->delete( 'watchlist', 00351 array( 00352 'wl_user' => $user->getId(), 00353 'wl_namespace' => $namespace, 00354 'wl_title' => $dbKey, 00355 ), 00356 __METHOD__ 00357 ); 00358 00359 // Can't just do an UPDATE instead of DELETE/INSERT due to unique index 00360 if ( $title ) { 00361 $user->addWatch( $title ); 00362 } 00363 } 00364 } 00365 00369 private function clearWatchlist() { 00370 $dbw = wfGetDB( DB_MASTER ); 00371 $dbw->delete( 00372 'watchlist', 00373 array( 'wl_user' => $this->getUser()->getId() ), 00374 __METHOD__ 00375 ); 00376 } 00377 00386 private function watchTitles( $titles ) { 00387 $dbw = wfGetDB( DB_MASTER ); 00388 $rows = array(); 00389 foreach( $titles as $title ) { 00390 if( !$title instanceof Title ) { 00391 $title = Title::newFromText( $title ); 00392 } 00393 if( $title instanceof Title ) { 00394 $rows[] = array( 00395 'wl_user' => $this->getUser()->getId(), 00396 'wl_namespace' => ( $title->getNamespace() & ~1 ), 00397 'wl_title' => $title->getDBkey(), 00398 'wl_notificationtimestamp' => null, 00399 ); 00400 $rows[] = array( 00401 'wl_user' => $this->getUser()->getId(), 00402 'wl_namespace' => ( $title->getNamespace() | 1 ), 00403 'wl_title' => $title->getDBkey(), 00404 'wl_notificationtimestamp' => null, 00405 ); 00406 } 00407 } 00408 $dbw->insert( 'watchlist', $rows, __METHOD__, 'IGNORE' ); 00409 } 00410 00419 private function unwatchTitles( $titles ) { 00420 $dbw = wfGetDB( DB_MASTER ); 00421 foreach( $titles as $title ) { 00422 if( !$title instanceof Title ) { 00423 $title = Title::newFromText( $title ); 00424 } 00425 if( $title instanceof Title ) { 00426 $dbw->delete( 00427 'watchlist', 00428 array( 00429 'wl_user' => $this->getUser()->getId(), 00430 'wl_namespace' => ( $title->getNamespace() & ~1 ), 00431 'wl_title' => $title->getDBkey(), 00432 ), 00433 __METHOD__ 00434 ); 00435 $dbw->delete( 00436 'watchlist', 00437 array( 00438 'wl_user' => $this->getUser()->getId(), 00439 'wl_namespace' => ( $title->getNamespace() | 1 ), 00440 'wl_title' => $title->getDBkey(), 00441 ), 00442 __METHOD__ 00443 ); 00444 $page = WikiPage::factory( $title ); 00445 wfRunHooks( 'UnwatchArticleComplete', array( $this->getUser(), &$page ) ); 00446 } 00447 } 00448 } 00449 00450 public function submitNormal( $data ) { 00451 $removed = array(); 00452 00453 foreach( $data as $titles ) { 00454 $this->unwatchTitles( $titles ); 00455 $removed = array_merge( $removed, $titles ); 00456 } 00457 00458 if( count( $removed ) > 0 ) { 00459 $this->successMessage = $this->msg( 'watchlistedit-normal-done' 00460 )->numParams( count( $removed ) )->parse(); 00461 $this->showTitles( $removed, $this->successMessage ); 00462 return true; 00463 } else { 00464 return false; 00465 } 00466 } 00467 00473 protected function getNormalForm(){ 00474 global $wgContLang; 00475 00476 $fields = array(); 00477 $count = 0; 00478 00479 foreach( $this->getWatchlistInfo() as $namespace => $pages ){ 00480 if ( $namespace >= 0 ) { 00481 $fields['TitlesNs'.$namespace] = array( 00482 'class' => 'EditWatchlistCheckboxSeriesField', 00483 'options' => array(), 00484 'section' => "ns$namespace", 00485 ); 00486 } 00487 00488 foreach( array_keys( $pages ) as $dbkey ){ 00489 $title = Title::makeTitleSafe( $namespace, $dbkey ); 00490 if ( $this->checkTitle( $title, $namespace, $dbkey ) ) { 00491 $text = $this->buildRemoveLine( $title ); 00492 $fields['TitlesNs'.$namespace]['options'][$text] = htmlspecialchars( $title->getPrefixedText() ); 00493 $count++; 00494 } 00495 } 00496 } 00497 $this->cleanupWatchlist(); 00498 00499 if ( count( $fields ) > 1 && $count > 30 ) { 00500 $this->toc = Linker::tocIndent(); 00501 $tocLength = 0; 00502 foreach( $fields as $data ) { 00503 00504 # strip out the 'ns' prefix from the section name: 00505 $ns = substr( $data['section'], 2 ); 00506 00507 $nsText = ($ns == NS_MAIN) 00508 ? $this->msg( 'blanknamespace' )->escaped() 00509 : htmlspecialchars( $wgContLang->getFormattedNsText( $ns ) ); 00510 $this->toc .= Linker::tocLine( "editwatchlist-{$data['section']}", $nsText, 00511 $this->getLanguage()->formatNum( ++$tocLength ), 1 ) . Linker::tocLineEnd(); 00512 } 00513 $this->toc = Linker::tocList( $this->toc ); 00514 } else { 00515 $this->toc = false; 00516 } 00517 00518 $form = new EditWatchlistNormalHTMLForm( $fields, $this->getContext() ); 00519 $form->setTitle( $this->getTitle() ); 00520 $form->setSubmitTextMsg( 'watchlistedit-normal-submit' ); 00521 # Used message keys: 'accesskey-watchlistedit-normal-submit', 'tooltip-watchlistedit-normal-submit' 00522 $form->setSubmitTooltip('watchlistedit-normal-submit'); 00523 $form->setWrapperLegendMsg( 'watchlistedit-normal-legend' ); 00524 $form->addHeaderText( $this->msg( 'watchlistedit-normal-explain' )->parse() ); 00525 $form->setSubmitCallback( array( $this, 'submitNormal' ) ); 00526 return $form; 00527 } 00528 00535 private function buildRemoveLine( $title ) { 00536 $link = Linker::link( $title ); 00537 if( $title->isRedirect() ) { 00538 // Linker already makes class mw-redirect, so this is redundant 00539 $link = '<span class="watchlistredir">' . $link . '</span>'; 00540 } 00541 $tools[] = Linker::link( $title->getTalkPage(), $this->msg( 'talkpagelinktext' )->escaped() ); 00542 if( $title->exists() ) { 00543 $tools[] = Linker::linkKnown( 00544 $title, 00545 $this->msg( 'history_short' )->escaped(), 00546 array(), 00547 array( 'action' => 'history' ) 00548 ); 00549 } 00550 if( $title->getNamespace() == NS_USER && !$title->isSubpage() ) { 00551 $tools[] = Linker::linkKnown( 00552 SpecialPage::getTitleFor( 'Contributions', $title->getText() ), 00553 $this->msg( 'contributions' )->escaped() 00554 ); 00555 } 00556 00557 wfRunHooks( 'WatchlistEditorBuildRemoveLine', array( &$tools, $title, $title->isRedirect(), $this->getSkin() ) ); 00558 00559 return $link . " (" . $this->getLanguage()->pipeList( $tools ) . ")"; 00560 } 00561 00567 protected function getRawForm(){ 00568 $titles = implode( $this->getWatchlist(), "\n" ); 00569 $fields = array( 00570 'Titles' => array( 00571 'type' => 'textarea', 00572 'label-message' => 'watchlistedit-raw-titles', 00573 'default' => $titles, 00574 ), 00575 ); 00576 $form = new HTMLForm( $fields, $this->getContext() ); 00577 $form->setTitle( $this->getTitle( 'raw' ) ); 00578 $form->setSubmitTextMsg( 'watchlistedit-raw-submit' ); 00579 # Used message keys: 'accesskey-watchlistedit-raw-submit', 'tooltip-watchlistedit-raw-submit' 00580 $form->setSubmitTooltip('watchlistedit-raw-submit'); 00581 $form->setWrapperLegendMsg( 'watchlistedit-raw-legend' ); 00582 $form->addHeaderText( $this->msg( 'watchlistedit-raw-explain' )->parse() ); 00583 $form->setSubmitCallback( array( $this, 'submitRaw' ) ); 00584 return $form; 00585 } 00586 00595 public static function getMode( $request, $par ) { 00596 $mode = strtolower( $request->getVal( 'action', $par ) ); 00597 switch( $mode ) { 00598 case 'clear': 00599 case self::EDIT_CLEAR: 00600 return self::EDIT_CLEAR; 00601 00602 case 'raw': 00603 case self::EDIT_RAW: 00604 return self::EDIT_RAW; 00605 00606 case 'edit': 00607 case self::EDIT_NORMAL: 00608 return self::EDIT_NORMAL; 00609 00610 default: 00611 return false; 00612 } 00613 } 00614 00622 public static function buildTools( $unused ) { 00623 global $wgLang; 00624 00625 $tools = array(); 00626 $modes = array( 00627 'view' => array( 'Watchlist', false ), 00628 'edit' => array( 'EditWatchlist', false ), 00629 'raw' => array( 'EditWatchlist', 'raw' ), 00630 ); 00631 foreach( $modes as $mode => $arr ) { 00632 // can use messages 'watchlisttools-view', 'watchlisttools-edit', 'watchlisttools-raw' 00633 $tools[] = Linker::linkKnown( 00634 SpecialPage::getTitleFor( $arr[0], $arr[1] ), 00635 wfMessage( "watchlisttools-{$mode}" )->escaped() 00636 ); 00637 } 00638 return Html::rawElement( 'span', 00639 array( 'class' => 'mw-watchlist-toollinks' ), 00640 wfMessage( 'parentheses', $wgLang->pipeList( $tools ) )->text() ); 00641 } 00642 } 00643 00644 # B/C since 1.18 00645 class WatchlistEditor extends SpecialEditWatchlist {} 00646 00650 class EditWatchlistNormalHTMLForm extends HTMLForm { 00651 public function getLegend( $namespace ){ 00652 $namespace = substr( $namespace, 2 ); 00653 return $namespace == NS_MAIN 00654 ? $this->msg( 'blanknamespace' )->escaped() 00655 : htmlspecialchars( $this->getContext()->getLanguage()->getFormattedNsText( $namespace ) ); 00656 } 00657 public function getBody() { 00658 return $this->displaySection( $this->mFieldTree, '', 'editwatchlist-' ); 00659 } 00660 } 00661 00662 class EditWatchlistCheckboxSeriesField extends HTMLMultiSelectField { 00674 function validate( $value, $alldata ) { 00675 // Need to call into grandparent to be a good citizen. :) 00676 return HTMLFormField::validate( $value, $alldata ); 00677 } 00678 }