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