MediaWiki  REL1_19
SpecialEditWatchlist.php
Go to the documentation of this file.
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 }