MediaWiki  REL1_24
SpecialWatchlist.php
Go to the documentation of this file.
00001 <?php
00030 class SpecialWatchlist extends ChangesListSpecialPage {
00031     public function __construct( $page = 'Watchlist', $restriction = 'viewmywatchlist' ) {
00032         parent::__construct( $page, $restriction );
00033     }
00034 
00040     function execute( $subpage ) {
00041         // Anons don't get a watchlist
00042         $this->requireLogin( 'watchlistanontext' );
00043 
00044         $output = $this->getOutput();
00045         $request = $this->getRequest();
00046 
00047         $mode = SpecialEditWatchlist::getMode( $request, $subpage );
00048         if ( $mode !== false ) {
00049             if ( $mode === SpecialEditWatchlist::EDIT_RAW ) {
00050                 $title = SpecialPage::getTitleFor( 'EditWatchlist', 'raw' );
00051             } elseif ( $mode === SpecialEditWatchlist::EDIT_CLEAR ) {
00052                 $title = SpecialPage::getTitleFor( 'EditWatchlist', 'clear' );
00053             } else {
00054                 $title = SpecialPage::getTitleFor( 'EditWatchlist' );
00055             }
00056 
00057             $output->redirect( $title->getLocalURL() );
00058 
00059             return;
00060         }
00061 
00062         $this->checkPermissions();
00063 
00064         $user = $this->getUser();
00065         $opts = $this->getOptions();
00066 
00067         $config = $this->getConfig();
00068         if ( ( $config->get( 'EnotifWatchlist' ) || $config->get( 'ShowUpdatedMarker' ) )
00069             && $request->getVal( 'reset' )
00070             && $request->wasPosted()
00071         ) {
00072             $user->clearAllNotifications();
00073             $output->redirect( $this->getPageTitle()->getFullURL( $opts->getChangedValues() ) );
00074 
00075             return;
00076         }
00077 
00078         parent::execute( $subpage );
00079     }
00080 
00088     public function prefixSearchSubpages( $search, $limit = 10 ) {
00089         // See also SpecialEditWatchlist::prefixSearchSubpages
00090         return self::prefixSearchArray(
00091             $search,
00092             $limit,
00093             array(
00094                 'clear',
00095                 'edit',
00096                 'raw',
00097             )
00098         );
00099     }
00100 
00106     public function getDefaultOptions() {
00107         $opts = parent::getDefaultOptions();
00108         $user = $this->getUser();
00109 
00110         $opts->add( 'days', $user->getOption( 'watchlistdays' ), FormOptions::FLOAT );
00111 
00112         $opts->add( 'hideminor', $user->getBoolOption( 'watchlisthideminor' ) );
00113         $opts->add( 'hidebots', $user->getBoolOption( 'watchlisthidebots' ) );
00114         $opts->add( 'hideanons', $user->getBoolOption( 'watchlisthideanons' ) );
00115         $opts->add( 'hideliu', $user->getBoolOption( 'watchlisthideliu' ) );
00116         $opts->add( 'hidepatrolled', $user->getBoolOption( 'watchlisthidepatrolled' ) );
00117         $opts->add( 'hidemyself', $user->getBoolOption( 'watchlisthideown' ) );
00118 
00119         $opts->add( 'extended', $user->getBoolOption( 'extendwatchlist' ) );
00120 
00121         return $opts;
00122     }
00123 
00129     protected function getCustomFilters() {
00130         if ( $this->customFilters === null ) {
00131             $this->customFilters = parent::getCustomFilters();
00132             wfRunHooks( 'SpecialWatchlistFilters', array( $this, &$this->customFilters ), '1.23' );
00133         }
00134 
00135         return $this->customFilters;
00136     }
00137 
00147     protected function fetchOptionsFromRequest( $opts ) {
00148         static $compatibilityMap = array(
00149             'hideMinor' => 'hideminor',
00150             'hideBots' => 'hidebots',
00151             'hideAnons' => 'hideanons',
00152             'hideLiu' => 'hideliu',
00153             'hidePatrolled' => 'hidepatrolled',
00154             'hideOwn' => 'hidemyself',
00155         );
00156 
00157         $params = $this->getRequest()->getValues();
00158         foreach ( $compatibilityMap as $from => $to ) {
00159             if ( isset( $params[$from] ) ) {
00160                 $params[$to] = $params[$from];
00161                 unset( $params[$from] );
00162             }
00163         }
00164 
00165         // Not the prettiest way to achieve this… FormOptions internally depends on data sanitization
00166         // methods defined on WebRequest and removing this dependency would cause some code duplication.
00167         $request = new DerivativeRequest( $this->getRequest(), $params );
00168         $opts->fetchValuesFromRequest( $request );
00169 
00170         return $opts;
00171     }
00172 
00179     public function buildMainQueryConds( FormOptions $opts ) {
00180         $dbr = $this->getDB();
00181         $conds = parent::buildMainQueryConds( $opts );
00182 
00183         // Calculate cutoff
00184         if ( $opts['days'] > 0 ) {
00185             $conds[] = 'rc_timestamp > ' .
00186                 $dbr->addQuotes( $dbr->timestamp( time() - intval( $opts['days'] * 86400 ) ) );
00187         }
00188 
00189         return $conds;
00190     }
00191 
00199     public function doMainQuery( $conds, $opts ) {
00200         $dbr = $this->getDB();
00201         $user = $this->getUser();
00202 
00203         # Toggle watchlist content (all recent edits or just the latest)
00204         if ( $opts['extended'] ) {
00205             $limitWatchlist = $user->getIntOption( 'wllimit' );
00206             $usePage = false;
00207         } else {
00208             # Top log Ids for a page are not stored
00209             $nonRevisionTypes = array( RC_LOG );
00210             wfRunHooks( 'SpecialWatchlistGetNonRevisionTypes', array( &$nonRevisionTypes ) );
00211             if ( $nonRevisionTypes ) {
00212                 $conds[] = $dbr->makeList(
00213                     array(
00214                         'rc_this_oldid=page_latest',
00215                         'rc_type' => $nonRevisionTypes,
00216                     ),
00217                     LIST_OR
00218                 );
00219             }
00220             $limitWatchlist = 0;
00221             $usePage = true;
00222         }
00223 
00224         $tables = array( 'recentchanges', 'watchlist' );
00225         $fields = RecentChange::selectFields();
00226         $query_options = array( 'ORDER BY' => 'rc_timestamp DESC' );
00227         $join_conds = array(
00228             'watchlist' => array(
00229                 'INNER JOIN',
00230                 array(
00231                     'wl_user' => $user->getId(),
00232                     'wl_namespace=rc_namespace',
00233                     'wl_title=rc_title'
00234                 ),
00235             ),
00236         );
00237 
00238         if ( $this->getConfig()->get( 'ShowUpdatedMarker' ) ) {
00239             $fields[] = 'wl_notificationtimestamp';
00240         }
00241         if ( $limitWatchlist ) {
00242             $query_options['LIMIT'] = $limitWatchlist;
00243         }
00244 
00245         $rollbacker = $user->isAllowed( 'rollback' );
00246         if ( $usePage || $rollbacker ) {
00247             $tables[] = 'page';
00248             $join_conds['page'] = array( 'LEFT JOIN', 'rc_cur_id=page_id' );
00249             if ( $rollbacker ) {
00250                 $fields[] = 'page_latest';
00251             }
00252         }
00253 
00254         // Log entries with DELETED_ACTION must not show up unless the user has
00255         // the necessary rights.
00256         if ( !$user->isAllowed( 'deletedhistory' ) ) {
00257             $bitmask = LogPage::DELETED_ACTION;
00258         } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
00259             $bitmask = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
00260         } else {
00261             $bitmask = 0;
00262         }
00263         if ( $bitmask ) {
00264             $conds[] = $dbr->makeList( array(
00265                 'rc_type != ' . RC_LOG,
00266                 $dbr->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask",
00267             ), LIST_OR );
00268         }
00269 
00270         ChangeTags::modifyDisplayQuery(
00271             $tables,
00272             $fields,
00273             $conds,
00274             $join_conds,
00275             $query_options,
00276             ''
00277         );
00278 
00279         $this->runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds, $opts );
00280 
00281         return $dbr->select(
00282             $tables,
00283             $fields,
00284             $conds,
00285             __METHOD__,
00286             $query_options,
00287             $join_conds
00288         );
00289     }
00290 
00291     protected function runMainQueryHook( &$tables, &$fields, &$conds, &$query_options, &$join_conds, $opts ) {
00292         return parent::runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds, $opts )
00293             && wfRunHooks(
00294                 'SpecialWatchlistQuery',
00295                 array( &$conds, &$tables, &$join_conds, &$fields, $opts ),
00296                 '1.23'
00297             );
00298     }
00299 
00305     protected function getDB() {
00306         return wfGetDB( DB_SLAVE, 'watchlist' );
00307     }
00308 
00312     public function outputFeedLinks() {
00313         $user = $this->getUser();
00314         $wlToken = $user->getTokenFromOption( 'watchlisttoken' );
00315         if ( $wlToken ) {
00316             $this->addFeedLinks( array(
00317                 'action' => 'feedwatchlist',
00318                 'allrev' => 1,
00319                 'wlowner' => $user->getName(),
00320                 'wltoken' => $wlToken,
00321             ) );
00322         }
00323     }
00324 
00331     public function outputChangesList( $rows, $opts ) {
00332         $dbr = $this->getDB();
00333         $user = $this->getUser();
00334         $output = $this->getOutput();
00335 
00336         # Show a message about slave lag, if applicable
00337         $lag = wfGetLB()->safeGetLag( $dbr );
00338         if ( $lag > 0 ) {
00339             $output->showLagWarning( $lag );
00340         }
00341 
00342         # If no rows to display, show message before try to render the list
00343         if ( $rows->numRows() == 0 ) {
00344             $output->wrapWikiMsg(
00345                 "<div class='mw-changeslist-empty'>\n$1\n</div>", 'recentchanges-noresult'
00346             );
00347             return;
00348         }
00349 
00350         $dbr->dataSeek( $rows, 0 );
00351 
00352         $list = ChangesList::newFromContext( $this->getContext() );
00353         $list->setWatchlistDivs();
00354         $list->initChangesListRows( $rows );
00355         $dbr->dataSeek( $rows, 0 );
00356 
00357         $s = $list->beginRecentChangesList();
00358         $counter = 1;
00359         foreach ( $rows as $obj ) {
00360             # Make RC entry
00361             $rc = RecentChange::newFromRow( $obj );
00362             $rc->counter = $counter++;
00363 
00364             if ( $this->getConfig()->get( 'ShowUpdatedMarker' ) ) {
00365                 $updated = $obj->wl_notificationtimestamp;
00366             } else {
00367                 $updated = false;
00368             }
00369 
00370             if ( $this->getConfig()->get( 'RCShowWatchingUsers' ) && $user->getOption( 'shownumberswatching' ) ) {
00371                 $rc->numberofWatchingusers = $dbr->selectField( 'watchlist',
00372                     'COUNT(*)',
00373                     array(
00374                         'wl_namespace' => $obj->rc_namespace,
00375                         'wl_title' => $obj->rc_title,
00376                     ),
00377                     __METHOD__ );
00378             } else {
00379                 $rc->numberofWatchingusers = 0;
00380             }
00381 
00382             $changeLine = $list->recentChangesLine( $rc, $updated, $counter );
00383             if ( $changeLine !== false ) {
00384                 $s .= $changeLine;
00385             }
00386         }
00387         $s .= $list->endRecentChangesList();
00388 
00389         $output->addHTML( $s );
00390     }
00391 
00398     public function doHeader( $opts, $numRows ) {
00399         $user = $this->getUser();
00400 
00401         $this->getOutput()->addSubtitle(
00402             $this->msg( 'watchlistfor2', $user->getName() )
00403                 ->rawParams( SpecialEditWatchlist::buildTools( null ) )
00404         );
00405 
00406         $this->setTopText( $opts );
00407 
00408         $lang = $this->getLanguage();
00409         $wlInfo = '';
00410         if ( $opts['days'] > 0 ) {
00411             $timestamp = wfTimestampNow();
00412             $wlInfo = $this->msg( 'wlnote' )->numParams( $numRows, round( $opts['days'] * 24 ) )->params(
00413                 $lang->userDate( $timestamp, $user ), $lang->userTime( $timestamp, $user )
00414             )->parse() . "<br />\n";
00415         }
00416 
00417         $nondefaults = $opts->getChangedValues();
00418         $cutofflinks = $this->cutoffLinks( $opts['days'], $nondefaults ) . "<br />\n";
00419 
00420         # Spit out some control panel links
00421         $filters = array(
00422             'hideminor' => 'rcshowhideminor',
00423             'hidebots' => 'rcshowhidebots',
00424             'hideanons' => 'rcshowhideanons',
00425             'hideliu' => 'rcshowhideliu',
00426             'hidemyself' => 'rcshowhidemine',
00427             'hidepatrolled' => 'rcshowhidepatr'
00428         );
00429         foreach ( $this->getCustomFilters() as $key => $params ) {
00430             $filters[$key] = $params['msg'];
00431         }
00432         // Disable some if needed
00433         if ( !$user->useNPPatrol() ) {
00434             unset( $filters['hidepatrolled'] );
00435         }
00436 
00437         $links = array();
00438         foreach ( $filters as $name => $msg ) {
00439             $links[] = $this->showHideLink( $nondefaults, $msg, $name, $opts[$name] );
00440         }
00441 
00442         $hiddenFields = $nondefaults;
00443         unset( $hiddenFields['namespace'] );
00444         unset( $hiddenFields['invert'] );
00445         unset( $hiddenFields['associated'] );
00446 
00447         # Create output
00448         $form = '';
00449 
00450         # Namespace filter and put the whole form together.
00451         $form .= $wlInfo;
00452         $form .= $cutofflinks;
00453         $form .= $lang->pipeList( $links ) . "\n";
00454         $form .= "<hr />\n<p>";
00455         $form .= Html::namespaceSelector(
00456             array(
00457                 'selected' => $opts['namespace'],
00458                 'all' => '',
00459                 'label' => $this->msg( 'namespace' )->text()
00460             ), array(
00461                 'name' => 'namespace',
00462                 'id' => 'namespace',
00463                 'class' => 'namespaceselector',
00464             )
00465         ) . '&#160;';
00466         $form .= Xml::checkLabel(
00467             $this->msg( 'invert' )->text(),
00468             'invert',
00469             'nsinvert',
00470             $opts['invert'],
00471             array( 'title' => $this->msg( 'tooltip-invert' )->text() )
00472         ) . '&#160;';
00473         $form .= Xml::checkLabel(
00474             $this->msg( 'namespace_association' )->text(),
00475             'associated',
00476             'nsassociated',
00477             $opts['associated'],
00478             array( 'title' => $this->msg( 'tooltip-namespace_association' )->text() )
00479         ) . '&#160;';
00480         $form .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . "</p>\n";
00481         foreach ( $hiddenFields as $key => $value ) {
00482             $form .= Html::hidden( $key, $value ) . "\n";
00483         }
00484         $form .= Xml::closeElement( 'fieldset' ) . "\n";
00485         $form .= Xml::closeElement( 'form' ) . "\n";
00486         $this->getOutput()->addHTML( $form );
00487 
00488         $this->setBottomText( $opts );
00489     }
00490 
00491     function setTopText( FormOptions $opts ) {
00492         $nondefaults = $opts->getChangedValues();
00493         $form = "";
00494         $user = $this->getUser();
00495 
00496         $dbr = $this->getDB();
00497         $numItems = $this->countItems( $dbr );
00498         $showUpdatedMarker = $this->getConfig()->get( 'ShowUpdatedMarker' );
00499 
00500         // Show watchlist header
00501         $form .= "<p>";
00502         if ( $numItems == 0 ) {
00503             $form .= $this->msg( 'nowatchlist' )->parse() . "\n";
00504         } else {
00505             $form .= $this->msg( 'watchlist-details' )->numParams( $numItems )->parse() . "\n";
00506             if ( $this->getConfig()->get( 'EnotifWatchlist' ) && $user->getOption( 'enotifwatchlistpages' ) ) {
00507                 $form .= $this->msg( 'wlheader-enotif' )->parse() . "\n";
00508             }
00509             if ( $showUpdatedMarker ) {
00510                 $form .= $this->msg( 'wlheader-showupdated' )->parse() . "\n";
00511             }
00512         }
00513         $form .= "</p>";
00514 
00515         if ( $numItems > 0 && $showUpdatedMarker ) {
00516             $form .= Xml::openElement( 'form', array( 'method' => 'post',
00517                 'action' => $this->getPageTitle()->getLocalURL(),
00518                 'id' => 'mw-watchlist-resetbutton' ) ) . "\n" .
00519             Xml::submitButton( $this->msg( 'enotif_reset' )->text(), array( 'name' => 'dummy' ) ) . "\n" .
00520             Html::hidden( 'reset', 'all' ) . "\n";
00521             foreach ( $nondefaults as $key => $value ) {
00522                 $form .= Html::hidden( $key, $value ) . "\n";
00523             }
00524             $form .= Xml::closeElement( 'form' ) . "\n";
00525         }
00526 
00527         $form .= Xml::openElement( 'form', array(
00528             'method' => 'post',
00529             'action' => $this->getPageTitle()->getLocalURL(),
00530             'id' => 'mw-watchlist-form'
00531         ) );
00532         $form .= Xml::fieldset(
00533             $this->msg( 'watchlist-options' )->text(),
00534             false,
00535             array( 'id' => 'mw-watchlist-options' )
00536         );
00537 
00538         $form .= SpecialRecentChanges::makeLegend( $this->getContext() );
00539 
00540         $this->getOutput()->addHTML( $form );
00541     }
00542 
00543     protected function showHideLink( $options, $message, $name, $value ) {
00544         $label = $this->msg( $value ? 'show' : 'hide' )->escaped();
00545         $options[$name] = 1 - (int)$value;
00546 
00547         return $this->msg( $message )
00548             ->rawParams( Linker::linkKnown( $this->getPageTitle(), $label, array(), $options ) )
00549             ->escaped();
00550     }
00551 
00552     protected function hoursLink( $h, $options = array() ) {
00553         $options['days'] = ( $h / 24.0 );
00554 
00555         return Linker::linkKnown(
00556             $this->getPageTitle(),
00557             $this->getLanguage()->formatNum( $h ),
00558             array(),
00559             $options
00560         );
00561     }
00562 
00563     protected function daysLink( $d, $options = array() ) {
00564         $options['days'] = $d;
00565         $message = $d ? $this->getLanguage()->formatNum( $d )
00566             : $this->msg( 'watchlistall2' )->escaped();
00567 
00568         return Linker::linkKnown(
00569             $this->getPageTitle(),
00570             $message,
00571             array(),
00572             $options
00573         );
00574     }
00575 
00583     protected function cutoffLinks( $days, $options = array() ) {
00584         $hours = array( 1, 2, 6, 12 );
00585         $days = array( 1, 3, 7 );
00586         $i = 0;
00587         foreach ( $hours as $h ) {
00588             $hours[$i++] = $this->hoursLink( $h, $options );
00589         }
00590         $i = 0;
00591         foreach ( $days as $d ) {
00592             $days[$i++] = $this->daysLink( $d, $options );
00593         }
00594 
00595         return $this->msg( 'wlshowlast' )->rawParams(
00596             $this->getLanguage()->pipeList( $hours ),
00597             $this->getLanguage()->pipeList( $days ),
00598             $this->daysLink( 0, $options ) )->parse();
00599     }
00600 
00607     protected function countItems( $dbr ) {
00608         # Fetch the raw count
00609         $rows = $dbr->select( 'watchlist', array( 'count' => 'COUNT(*)' ),
00610             array( 'wl_user' => $this->getUser()->getId() ), __METHOD__ );
00611         $row = $dbr->fetchObject( $rows );
00612         $count = $row->count;
00613 
00614         return floor( $count / 2 );
00615     }
00616 }