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