MediaWiki  REL1_20
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 
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 }