MediaWiki
REL1_19
|
00001 <?php 00002 00010 class SpecialEditWatchlist extends UnlistedSpecialPage { 00011 00015 const EDIT_CLEAR = 1; 00016 const EDIT_RAW = 2; 00017 const EDIT_NORMAL = 3; 00018 00019 protected $successMessage; 00020 00021 protected $toc; 00022 00023 private $badItems = array(); 00024 00025 public function __construct(){ 00026 parent::__construct( 'EditWatchlist' ); 00027 } 00028 00034 public function execute( $mode ) { 00035 $this->setHeaders(); 00036 00037 $out = $this->getOutput(); 00038 00039 # Anons don't get a watchlist 00040 if( $this->getUser()->isAnon() ) { 00041 $out->setPageTitle( $this->msg( 'watchnologin' ) ); 00042 $llink = Linker::linkKnown( 00043 SpecialPage::getTitleFor( 'Userlogin' ), 00044 $this->msg( 'loginreqlink' )->escaped(), 00045 array(), 00046 array( 'returnto' => $this->getTitle()->getPrefixedText() ) 00047 ); 00048 $out->addHTML( $this->msg( 'watchlistanontext' )->rawParams( $llink )->parse() ); 00049 return; 00050 } 00051 00052 $this->checkPermissions(); 00053 00054 $this->outputHeader(); 00055 00056 $out->addSubtitle( $this->msg( 'watchlistfor2', $this->getUser()->getName() 00057 )->rawParams( SpecialEditWatchlist::buildTools( null ) ) ); 00058 00059 # B/C: $mode used to be waaay down the parameter list, and the first parameter 00060 # was $wgUser 00061 if( $mode instanceof User ){ 00062 $args = func_get_args(); 00063 if( count( $args >= 4 ) ){ 00064 $mode = $args[3]; 00065 } 00066 } 00067 $mode = self::getMode( $this->getRequest(), $mode ); 00068 00069 switch( $mode ) { 00070 case self::EDIT_CLEAR: 00071 // The "Clear" link scared people too much. 00072 // Pass on to the raw editor, from which it's very easy to clear. 00073 00074 case self::EDIT_RAW: 00075 $out->setPageTitle( $this->msg( 'watchlistedit-raw-title' ) ); 00076 $form = $this->getRawForm(); 00077 if( $form->show() ){ 00078 $out->addHTML( $this->successMessage ); 00079 $out->returnToMain(); 00080 } 00081 break; 00082 00083 case self::EDIT_NORMAL: 00084 default: 00085 $out->setPageTitle( $this->msg( 'watchlistedit-normal-title' ) ); 00086 $form = $this->getNormalForm(); 00087 if( $form->show() ){ 00088 $out->addHTML( $this->successMessage ); 00089 $out->returnToMain(); 00090 } elseif ( $this->toc !== false ) { 00091 $out->prependHTML( $this->toc ); 00092 } 00093 break; 00094 } 00095 } 00096 00104 private function extractTitles( $list ) { 00105 $titles = array(); 00106 $list = explode( "\n", trim( $list ) ); 00107 if( !is_array( $list ) ) { 00108 return array(); 00109 } 00110 foreach( $list as $text ) { 00111 $text = trim( $text ); 00112 if( strlen( $text ) > 0 ) { 00113 $title = Title::newFromText( $text ); 00114 if( $title instanceof Title && $title->isWatchable() ) { 00115 $titles[] = $title->getPrefixedText(); 00116 } 00117 } 00118 } 00119 return array_unique( $titles ); 00120 } 00121 00122 public function submitRaw( $data ){ 00123 $wanted = $this->extractTitles( $data['Titles'] ); 00124 $current = $this->getWatchlist(); 00125 00126 if( count( $wanted ) > 0 ) { 00127 $toWatch = array_diff( $wanted, $current ); 00128 $toUnwatch = array_diff( $current, $wanted ); 00129 $this->watchTitles( $toWatch ); 00130 $this->unwatchTitles( $toUnwatch ); 00131 $this->getUser()->invalidateCache(); 00132 00133 if( count( $toWatch ) > 0 || count( $toUnwatch ) > 0 ){ 00134 $this->successMessage = $this->msg( 'watchlistedit-raw-done' )->parse(); 00135 } else { 00136 return false; 00137 } 00138 00139 if( count( $toWatch ) > 0 ) { 00140 $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-added' 00141 )->numParams( count( $toWatch ) )->parse(); 00142 $this->showTitles( $toWatch, $this->successMessage ); 00143 } 00144 00145 if( count( $toUnwatch ) > 0 ) { 00146 $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-removed' 00147 )->numParams( count( $toUnwatch ) )->parse(); 00148 $this->showTitles( $toUnwatch, $this->successMessage ); 00149 } 00150 } else { 00151 $this->clearWatchlist(); 00152 $this->getUser()->invalidateCache(); 00153 00154 if( count( $current ) > 0 ){ 00155 $this->successMessage = $this->msg( 'watchlistedit-raw-done' )->parse(); 00156 } else { 00157 return false; 00158 } 00159 00160 $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-removed' 00161 )->numParams( count( $current ) )->parse(); 00162 $this->showTitles( $current, $this->successMessage ); 00163 } 00164 return true; 00165 } 00166 00176 private function showTitles( $titles, &$output ) { 00177 $talk = $this->msg( 'talkpagelinktext' )->escaped(); 00178 // Do a batch existence check 00179 $batch = new LinkBatch(); 00180 foreach( $titles as $title ) { 00181 if( !$title instanceof Title ) { 00182 $title = Title::newFromText( $title ); 00183 } 00184 if( $title instanceof Title ) { 00185 $batch->addObj( $title ); 00186 $batch->addObj( $title->getTalkPage() ); 00187 } 00188 } 00189 $batch->execute(); 00190 // Print out the list 00191 $output .= "<ul>\n"; 00192 foreach( $titles as $title ) { 00193 if( !$title instanceof Title ) { 00194 $title = Title::newFromText( $title ); 00195 } 00196 if( $title instanceof Title ) { 00197 $output .= "<li>" 00198 . Linker::link( $title ) 00199 . ' (' . Linker::link( $title->getTalkPage(), $talk ) 00200 . ")</li>\n"; 00201 } 00202 } 00203 $output .= "</ul>\n"; 00204 } 00205 00212 private function getWatchlist() { 00213 $list = array(); 00214 $dbr = wfGetDB( DB_MASTER ); 00215 $res = $dbr->select( 00216 'watchlist', 00217 '*', 00218 array( 00219 'wl_user' => $this->getUser()->getId(), 00220 ), 00221 __METHOD__ 00222 ); 00223 if( $res->numRows() > 0 ) { 00224 foreach ( $res as $row ) { 00225 $title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title ); 00226 if ( $this->checkTitle( $title, $row->wl_namespace, $row->wl_title ) 00227 && !$title->isTalkPage() 00228 ) { 00229 $list[] = $title->getPrefixedText(); 00230 } 00231 } 00232 $res->free(); 00233 } 00234 $this->cleanupWatchlist(); 00235 return $list; 00236 } 00237 00244 private function getWatchlistInfo() { 00245 $titles = array(); 00246 $dbr = wfGetDB( DB_MASTER ); 00247 00248 $res = $dbr->select( 00249 array( 'watchlist' ), 00250 array( 'wl_namespace', 'wl_title' ), 00251 array( 'wl_user' => $this->getUser()->getId() ), 00252 __METHOD__, 00253 array( 'ORDER BY' => 'wl_namespace, wl_title' ) 00254 ); 00255 00256 $lb = new LinkBatch(); 00257 foreach ( $res as $row ) { 00258 $lb->add( $row->wl_namespace, $row->wl_title ); 00259 if ( !MWNamespace::isTalk( $row->wl_namespace ) ) { 00260 $titles[$row->wl_namespace][$row->wl_title] = 1; 00261 } 00262 } 00263 00264 $lb->execute(); 00265 return $titles; 00266 } 00267 00276 private function checkTitle( $title, $namespace, $dbKey ) { 00277 if ( $title 00278 && ( $title->isExternal() 00279 || $title->getNamespace() < 0 00280 ) 00281 ) { 00282 $title = false; // unrecoverable 00283 } 00284 if ( !$title 00285 || $title->getNamespace() != $namespace 00286 || $title->getDBkey() != $dbKey 00287 ) { 00288 $this->badItems[] = array( $title, $namespace, $dbKey ); 00289 } 00290 return (bool)$title; 00291 } 00292 00296 private function cleanupWatchlist() { 00297 if ( count( $this->badItems ) ) { 00298 $dbw = wfGetDB( DB_MASTER ); 00299 } 00300 foreach ( $this->badItems as $row ) { 00301 list( $title, $namespace, $dbKey ) = $row; 00302 wfDebug( "User {$this->getUser()} has broken watchlist item ns($namespace):$dbKey, " 00303 . ( $title ? 'cleaning up' : 'deleting' ) . ".\n" 00304 ); 00305 00306 $dbw->delete( 'watchlist', 00307 array( 00308 'wl_user' => $this->getUser()->getId(), 00309 'wl_namespace' => $namespace, 00310 'wl_title' => $dbKey, 00311 ), 00312 __METHOD__ 00313 ); 00314 00315 // Can't just do an UPDATE instead of DELETE/INSERT due to unique index 00316 if ( $title ) { 00317 $this->getUser()->addWatch( $title ); 00318 } 00319 } 00320 } 00321 00325 private function clearWatchlist() { 00326 $dbw = wfGetDB( DB_MASTER ); 00327 $dbw->delete( 00328 'watchlist', 00329 array( 'wl_user' => $this->getUser()->getId() ), 00330 __METHOD__ 00331 ); 00332 } 00333 00342 private function watchTitles( $titles ) { 00343 $dbw = wfGetDB( DB_MASTER ); 00344 $rows = array(); 00345 foreach( $titles as $title ) { 00346 if( !$title instanceof Title ) { 00347 $title = Title::newFromText( $title ); 00348 } 00349 if( $title instanceof Title ) { 00350 $rows[] = array( 00351 'wl_user' => $this->getUser()->getId(), 00352 'wl_namespace' => ( $title->getNamespace() & ~1 ), 00353 'wl_title' => $title->getDBkey(), 00354 'wl_notificationtimestamp' => null, 00355 ); 00356 $rows[] = array( 00357 'wl_user' => $this->getUser()->getId(), 00358 'wl_namespace' => ( $title->getNamespace() | 1 ), 00359 'wl_title' => $title->getDBkey(), 00360 'wl_notificationtimestamp' => null, 00361 ); 00362 } 00363 } 00364 $dbw->insert( 'watchlist', $rows, __METHOD__, 'IGNORE' ); 00365 } 00366 00375 private function unwatchTitles( $titles ) { 00376 $dbw = wfGetDB( DB_MASTER ); 00377 foreach( $titles as $title ) { 00378 if( !$title instanceof Title ) { 00379 $title = Title::newFromText( $title ); 00380 } 00381 if( $title instanceof Title ) { 00382 $dbw->delete( 00383 'watchlist', 00384 array( 00385 'wl_user' => $this->getUser()->getId(), 00386 'wl_namespace' => ( $title->getNamespace() & ~1 ), 00387 'wl_title' => $title->getDBkey(), 00388 ), 00389 __METHOD__ 00390 ); 00391 $dbw->delete( 00392 'watchlist', 00393 array( 00394 'wl_user' => $this->getUser()->getId(), 00395 'wl_namespace' => ( $title->getNamespace() | 1 ), 00396 'wl_title' => $title->getDBkey(), 00397 ), 00398 __METHOD__ 00399 ); 00400 $page = WikiPage::factory( $title ); 00401 wfRunHooks( 'UnwatchArticleComplete', array( $this->getUser(), &$page ) ); 00402 } 00403 } 00404 } 00405 00406 public function submitNormal( $data ) { 00407 $removed = array(); 00408 00409 foreach( $data as $titles ) { 00410 $this->unwatchTitles( $titles ); 00411 $removed += $titles; 00412 } 00413 00414 if( count( $removed ) > 0 ) { 00415 $this->successMessage = $this->msg( 'watchlistedit-normal-done' 00416 )->numParams( count( $removed ) )->parse(); 00417 $this->showTitles( $removed, $this->successMessage ); 00418 return true; 00419 } else { 00420 return false; 00421 } 00422 } 00423 00429 protected function getNormalForm(){ 00430 global $wgContLang; 00431 00432 $fields = array(); 00433 $count = 0; 00434 00435 foreach( $this->getWatchlistInfo() as $namespace => $pages ){ 00436 if ( $namespace >= 0 ) { 00437 $fields['TitlesNs'.$namespace] = array( 00438 'class' => 'EditWatchlistCheckboxSeriesField', 00439 'options' => array(), 00440 'section' => "ns$namespace", 00441 ); 00442 } 00443 00444 foreach( array_keys( $pages ) as $dbkey ){ 00445 $title = Title::makeTitleSafe( $namespace, $dbkey ); 00446 if ( $this->checkTitle( $title, $namespace, $dbkey ) ) { 00447 $text = $this->buildRemoveLine( $title ); 00448 $fields['TitlesNs'.$namespace]['options'][$text] = $title->getEscapedText(); 00449 $count++; 00450 } 00451 } 00452 } 00453 $this->cleanupWatchlist(); 00454 00455 if ( count( $fields ) > 1 && $count > 30 ) { 00456 $this->toc = Linker::tocIndent(); 00457 $tocLength = 0; 00458 foreach( $fields as $key => $data ) { 00459 00460 # strip out the 'ns' prefix from the section name: 00461 $ns = substr( $data['section'], 2 ); 00462 00463 $nsText = ($ns == NS_MAIN) 00464 ? $this->msg( 'blanknamespace' )->escaped() 00465 : htmlspecialchars( $wgContLang->getFormattedNsText( $ns ) ); 00466 $this->toc .= Linker::tocLine( "editwatchlist-{$data['section']}", $nsText, 00467 $this->getLanguage()->formatNum( ++$tocLength ), 1 ) . Linker::tocLineEnd(); 00468 } 00469 $this->toc = Linker::tocList( $this->toc ); 00470 } else { 00471 $this->toc = false; 00472 } 00473 00474 $form = new EditWatchlistNormalHTMLForm( $fields, $this->getContext() ); 00475 $form->setTitle( $this->getTitle() ); 00476 $form->setSubmitTextMsg( 'watchlistedit-normal-submit' ); 00477 # Used message keys: 'accesskey-watchlistedit-normal-submit', 'tooltip-watchlistedit-normal-submit' 00478 $form->setSubmitTooltip('watchlistedit-normal-submit'); 00479 $form->setWrapperLegendMsg( 'watchlistedit-normal-legend' ); 00480 $form->addHeaderText( $this->msg( 'watchlistedit-normal-explain' )->parse() ); 00481 $form->setSubmitCallback( array( $this, 'submitNormal' ) ); 00482 return $form; 00483 } 00484 00491 private function buildRemoveLine( $title ) { 00492 $link = Linker::link( $title ); 00493 if( $title->isRedirect() ) { 00494 // Linker already makes class mw-redirect, so this is redundant 00495 $link = '<span class="watchlistredir">' . $link . '</span>'; 00496 } 00497 $tools[] = Linker::link( $title->getTalkPage(), $this->msg( 'talkpagelinktext' )->escaped() ); 00498 if( $title->exists() ) { 00499 $tools[] = Linker::linkKnown( 00500 $title, 00501 $this->msg( 'history_short' )->escaped(), 00502 array(), 00503 array( 'action' => 'history' ) 00504 ); 00505 } 00506 if( $title->getNamespace() == NS_USER && !$title->isSubpage() ) { 00507 $tools[] = Linker::linkKnown( 00508 SpecialPage::getTitleFor( 'Contributions', $title->getText() ), 00509 $this->msg( 'contributions' )->escaped() 00510 ); 00511 } 00512 00513 wfRunHooks( 'WatchlistEditorBuildRemoveLine', array( &$tools, $title, $title->isRedirect(), $this->getSkin() ) ); 00514 00515 return $link . " (" . $this->getLanguage()->pipeList( $tools ) . ")"; 00516 } 00517 00523 protected function getRawForm(){ 00524 $titles = implode( $this->getWatchlist(), "\n" ); 00525 $fields = array( 00526 'Titles' => array( 00527 'type' => 'textarea', 00528 'label-message' => 'watchlistedit-raw-titles', 00529 'default' => $titles, 00530 ), 00531 ); 00532 $form = new HTMLForm( $fields, $this->getContext() ); 00533 $form->setTitle( $this->getTitle( 'raw' ) ); 00534 $form->setSubmitTextMsg( 'watchlistedit-raw-submit' ); 00535 # Used message keys: 'accesskey-watchlistedit-raw-submit', 'tooltip-watchlistedit-raw-submit' 00536 $form->setSubmitTooltip('watchlistedit-raw-submit'); 00537 $form->setWrapperLegendMsg( 'watchlistedit-raw-legend' ); 00538 $form->addHeaderText( $this->msg( 'watchlistedit-raw-explain' )->parse() ); 00539 $form->setSubmitCallback( array( $this, 'submitRaw' ) ); 00540 return $form; 00541 } 00542 00551 public static function getMode( $request, $par ) { 00552 $mode = strtolower( $request->getVal( 'action', $par ) ); 00553 switch( $mode ) { 00554 case 'clear': 00555 case self::EDIT_CLEAR: 00556 return self::EDIT_CLEAR; 00557 00558 case 'raw': 00559 case self::EDIT_RAW: 00560 return self::EDIT_RAW; 00561 00562 case 'edit': 00563 case self::EDIT_NORMAL: 00564 return self::EDIT_NORMAL; 00565 00566 default: 00567 return false; 00568 } 00569 } 00570 00578 public static function buildTools( $unused ) { 00579 global $wgLang; 00580 00581 $tools = array(); 00582 $modes = array( 00583 'view' => array( 'Watchlist', false ), 00584 'edit' => array( 'EditWatchlist', false ), 00585 'raw' => array( 'EditWatchlist', 'raw' ), 00586 ); 00587 foreach( $modes as $mode => $arr ) { 00588 // can use messages 'watchlisttools-view', 'watchlisttools-edit', 'watchlisttools-raw' 00589 $tools[] = Linker::linkKnown( 00590 SpecialPage::getTitleFor( $arr[0], $arr[1] ), 00591 wfMsgHtml( "watchlisttools-{$mode}" ) 00592 ); 00593 } 00594 return Html::rawElement( 'span', 00595 array( 'class' => 'mw-watchlist-toollinks' ), 00596 wfMsg( 'parentheses', $wgLang->pipeList( $tools ) ) ); 00597 } 00598 } 00599 00600 # B/C since 1.18 00601 class WatchlistEditor extends SpecialEditWatchlist {} 00602 00606 class EditWatchlistNormalHTMLForm extends HTMLForm { 00607 public function getLegend( $namespace ){ 00608 $namespace = substr( $namespace, 2 ); 00609 return $namespace == NS_MAIN 00610 ? $this->msg( 'blanknamespace' )->escaped() 00611 : htmlspecialchars( $this->getContext()->getLanguage()->getFormattedNsText( $namespace ) ); 00612 } 00613 public function getBody() { 00614 return $this->displaySection( $this->mFieldTree, '', 'editwatchlist-' ); 00615 } 00616 } 00617 00618 class EditWatchlistCheckboxSeriesField extends HTMLMultiSelectField { 00630 function validate( $value, $alldata ) { 00631 // Need to call into grandparent to be a good citizen. :) 00632 return HTMLFormField::validate( $value, $alldata ); 00633 } 00634 }