MediaWiki  REL1_22
SpecialWatchlist.php
Go to the documentation of this file.
00001 <?php
00023 class SpecialWatchlist extends SpecialPage {
00024     protected $customFilters;
00025 
00029     public function __construct( $page = 'Watchlist', $restriction = 'viewmywatchlist' ) {
00030         parent::__construct( $page, $restriction );
00031     }
00032 
00037     function execute( $par ) {
00038         global $wgRCShowWatchingUsers, $wgEnotifWatchlist, $wgShowUpdatedMarker;
00039 
00040         $user = $this->getUser();
00041         $output = $this->getOutput();
00042 
00043         # Anons don't get a watchlist
00044         if ( $user->isAnon() ) {
00045             $output->setPageTitle( $this->msg( 'watchnologin' ) );
00046             $output->setRobotPolicy( 'noindex,nofollow' );
00047             $llink = Linker::linkKnown(
00048                 SpecialPage::getTitleFor( 'Userlogin' ),
00049                 $this->msg( 'loginreqlink' )->escaped(),
00050                 array(),
00051                 array( 'returnto' => $this->getTitle()->getPrefixedText() )
00052             );
00053             $output->addHTML( $this->msg( 'watchlistanontext' )->rawParams( $llink )->parse() );
00054             return;
00055         }
00056 
00057         // Check permissions
00058         $this->checkPermissions();
00059 
00060         // Add feed links
00061         $wlToken = $user->getTokenFromOption( 'watchlisttoken' );
00062         if ( $wlToken ) {
00063             $this->addFeedLinks( array( 'action' => 'feedwatchlist', 'allrev' => 'allrev',
00064                                 'wlowner' => $user->getName(), 'wltoken' => $wlToken ) );
00065         }
00066 
00067         $this->setHeaders();
00068         $this->outputHeader();
00069 
00070         $output->addSubtitle( $this->msg( 'watchlistfor2', $user->getName()
00071             )->rawParams( SpecialEditWatchlist::buildTools( null ) ) );
00072 
00073         $request = $this->getRequest();
00074 
00075         $mode = SpecialEditWatchlist::getMode( $request, $par );
00076         if ( $mode !== false ) {
00077             # TODO: localise?
00078             switch ( $mode ) {
00079                 case SpecialEditWatchlist::EDIT_CLEAR:
00080                     $mode = 'clear';
00081                     break;
00082                 case SpecialEditWatchlist::EDIT_RAW:
00083                     $mode = 'raw';
00084                     break;
00085                 default:
00086                     $mode = null;
00087             }
00088             $title = SpecialPage::getTitleFor( 'EditWatchlist', $mode );
00089             $output->redirect( $title->getLocalURL() );
00090             return;
00091         }
00092 
00093         $dbr = wfGetDB( DB_SLAVE, 'watchlist' );
00094 
00095         $nitems = $this->countItems( $dbr );
00096         if ( $nitems == 0 ) {
00097             $output->addWikiMsg( 'nowatchlist' );
00098             return;
00099         }
00100 
00101         // @todo use FormOptions!
00102         $defaults = array(
00103         /* float */ 'days' => floatval( $user->getOption( 'watchlistdays' ) ),
00104         /* bool  */ 'hideMinor' => (int)$user->getBoolOption( 'watchlisthideminor' ),
00105         /* bool  */ 'hideBots' => (int)$user->getBoolOption( 'watchlisthidebots' ),
00106         /* bool  */ 'hideAnons' => (int)$user->getBoolOption( 'watchlisthideanons' ),
00107         /* bool  */ 'hideLiu' => (int)$user->getBoolOption( 'watchlisthideliu' ),
00108         /* bool  */ 'hidePatrolled' => (int)$user->getBoolOption( 'watchlisthidepatrolled' ),
00109         /* bool  */ 'hideOwn' => (int)$user->getBoolOption( 'watchlisthideown' ),
00110         /* bool  */ 'extended' => (int)$user->getBoolOption( 'extendwatchlist' ),
00111         /* ?     */ 'namespace' => '', //means all
00112         /* ?     */ 'invert' => false,
00113         /* bool  */ 'associated' => false,
00114         );
00115         $this->customFilters = array();
00116         wfRunHooks( 'SpecialWatchlistFilters', array( $this, &$this->customFilters ) );
00117         foreach ( $this->customFilters as $key => $params ) {
00118             $defaults[$key] = $params['default'];
00119         }
00120 
00121         # Extract variables from the request, falling back to user preferences or
00122         # other default values if these don't exist
00123         $values = array();
00124         $values['days'] = floatval( $request->getVal( 'days', $defaults['days'] ) );
00125         $values['hideMinor'] = (int)$request->getBool( 'hideMinor', $defaults['hideMinor'] );
00126         $values['hideBots'] = (int)$request->getBool( 'hideBots', $defaults['hideBots'] );
00127         $values['hideAnons'] = (int)$request->getBool( 'hideAnons', $defaults['hideAnons'] );
00128         $values['hideLiu'] = (int)$request->getBool( 'hideLiu', $defaults['hideLiu'] );
00129         $values['hideOwn'] = (int)$request->getBool( 'hideOwn', $defaults['hideOwn'] );
00130         $values['hidePatrolled'] = (int)$request->getBool( 'hidePatrolled', $defaults['hidePatrolled'] );
00131         $values['extended'] = (int)$request->getBool( 'extended', $defaults['extended'] );
00132         foreach ( $this->customFilters as $key => $params ) {
00133             $values[$key] = (int)$request->getBool( $key, $defaults[$key] );
00134         }
00135 
00136         # Get namespace value, if supplied, and prepare a WHERE fragment
00137         $nameSpace = $request->getIntOrNull( 'namespace' );
00138         $invert = $request->getBool( 'invert' );
00139         $associated = $request->getBool( 'associated' );
00140         if ( !is_null( $nameSpace ) ) {
00141             $eq_op = $invert ? '!=' : '=';
00142             $bool_op = $invert ? 'AND' : 'OR';
00143             $nameSpace = intval( $nameSpace ); // paranioa
00144             if ( !$associated ) {
00145                 $nameSpaceClause = "rc_namespace $eq_op $nameSpace";
00146             } else {
00147                 $associatedNS = MWNamespace::getAssociated( $nameSpace );
00148                 $nameSpaceClause =
00149                     "rc_namespace $eq_op $nameSpace " .
00150                     $bool_op .
00151                     " rc_namespace $eq_op $associatedNS";
00152             }
00153         } else {
00154             $nameSpace = '';
00155             $nameSpaceClause = '';
00156         }
00157         $values['namespace'] = $nameSpace;
00158         $values['invert'] = $invert;
00159         $values['associated'] = $associated;
00160 
00161         // Dump everything here
00162         $nondefaults = array();
00163         foreach ( $defaults as $name => $defValue ) {
00164             wfAppendToArrayIfNotDefault( $name, $values[$name], $defaults, $nondefaults );
00165         }
00166 
00167         if ( ( $wgEnotifWatchlist || $wgShowUpdatedMarker ) && $request->getVal( 'reset' ) &&
00168             $request->wasPosted() )
00169         {
00170             $user->clearAllNotifications();
00171             $output->redirect( $this->getTitle()->getFullURL( $nondefaults ) );
00172             return;
00173         }
00174 
00175         # Possible where conditions
00176         $conds = array();
00177 
00178         if ( $values['days'] > 0 ) {
00179             $conds[] = 'rc_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( time() - intval( $values['days'] * 86400 ) ) );
00180         }
00181 
00182         # Toggles
00183         if ( $values['hideOwn'] ) {
00184             $conds[] = 'rc_user != ' . $user->getId();
00185         }
00186         if ( $values['hideBots'] ) {
00187             $conds[] = 'rc_bot = 0';
00188         }
00189         if ( $values['hideMinor'] ) {
00190             $conds[] = 'rc_minor = 0';
00191         }
00192         if ( $values['hideLiu'] ) {
00193             $conds[] = 'rc_user = 0';
00194         }
00195         if ( $values['hideAnons'] ) {
00196             $conds[] = 'rc_user != 0';
00197         }
00198         if ( $user->useRCPatrol() && $values['hidePatrolled'] ) {
00199             $conds[] = 'rc_patrolled != 1';
00200         }
00201         if ( $nameSpaceClause ) {
00202             $conds[] = $nameSpaceClause;
00203         }
00204 
00205         # Toggle watchlist content (all recent edits or just the latest)
00206         if ( $values['extended'] ) {
00207             $limitWatchlist = $user->getIntOption( 'wllimit' );
00208             $usePage = false;
00209         } else {
00210             # Top log Ids for a page are not stored
00211             $nonRevisionTypes = array( RC_LOG );
00212             wfRunHooks( 'SpecialWatchlistGetNonRevisionTypes', array( &$nonRevisionTypes ) );
00213             if ( $nonRevisionTypes ) {
00214                 if ( count( $nonRevisionTypes ) === 1 ) {
00215                     // if only one use an equality instead of IN condition
00216                     $nonRevisionTypes = reset( $nonRevisionTypes );
00217                 }
00218                 $conds[] = $dbr->makeList(
00219                     array(
00220                         'rc_this_oldid=page_latest',
00221                         'rc_type' => $nonRevisionTypes,
00222                     ),
00223                     LIST_OR
00224                 );
00225             }
00226             $limitWatchlist = 0;
00227             $usePage = true;
00228         }
00229 
00230         # Show a message about slave lag, if applicable
00231         $lag = wfGetLB()->safeGetLag( $dbr );
00232         if ( $lag > 0 ) {
00233             $output->showLagWarning( $lag );
00234         }
00235 
00236         # Create output
00237         $form = '';
00238 
00239         # Show watchlist header
00240         $form .= "<p>";
00241         $form .= $this->msg( 'watchlist-details' )->numParams( $nitems )->parse() . "\n";
00242         if ( $wgEnotifWatchlist && $user->getOption( 'enotifwatchlistpages' ) ) {
00243             $form .= $this->msg( 'wlheader-enotif' )->parse() . "\n";
00244         }
00245         if ( $wgShowUpdatedMarker ) {
00246             $form .= $this->msg( 'wlheader-showupdated' )->parse() . "\n";
00247         }
00248         $form .= "</p>";
00249 
00250         if ( $wgShowUpdatedMarker ) {
00251             $form .= Xml::openElement( 'form', array( 'method' => 'post',
00252                 'action' => $this->getTitle()->getLocalURL(),
00253                 'id' => 'mw-watchlist-resetbutton' ) ) . "\n" .
00254             Xml::submitButton( $this->msg( 'enotif_reset' )->text(), array( 'name' => 'dummy' ) ) . "\n" .
00255             Html::hidden( 'reset', 'all' ) . "\n";
00256             foreach ( $nondefaults as $key => $value ) {
00257                 $form .= Html::hidden( $key, $value ) . "\n";
00258             }
00259             $form .= Xml::closeElement( 'form' ) . "\n";
00260         }
00261 
00262         $form .= Xml::openElement( 'form', array(
00263             'method' => 'post',
00264             'action' => $this->getTitle()->getLocalURL(),
00265             'id' => 'mw-watchlist-form'
00266         ) );
00267         $form .= Xml::fieldset(
00268             $this->msg( 'watchlist-options' )->text(),
00269             false,
00270             array( 'id' => 'mw-watchlist-options' )
00271         );
00272 
00273         $tables = array( 'recentchanges', 'watchlist' );
00274         $fields = RecentChange::selectFields();
00275         $join_conds = array(
00276             'watchlist' => array(
00277                 'INNER JOIN',
00278                 array(
00279                     'wl_user' => $user->getId(),
00280                     'wl_namespace=rc_namespace',
00281                     'wl_title=rc_title'
00282                 ),
00283             ),
00284         );
00285         $options = array( 'ORDER BY' => 'rc_timestamp DESC' );
00286         if ( $wgShowUpdatedMarker ) {
00287             $fields[] = 'wl_notificationtimestamp';
00288         }
00289         if ( $limitWatchlist ) {
00290             $options['LIMIT'] = $limitWatchlist;
00291         }
00292 
00293         $rollbacker = $user->isAllowed( 'rollback' );
00294         if ( $usePage || $rollbacker ) {
00295             $tables[] = 'page';
00296             $join_conds['page'] = array( 'LEFT JOIN', 'rc_cur_id=page_id' );
00297             if ( $rollbacker ) {
00298                 $fields[] = 'page_latest';
00299             }
00300         }
00301 
00302         // Log entries with DELETED_ACTION must not show up unless the user has
00303         // the necessary rights.
00304         if ( !$user->isAllowed( 'deletedhistory' ) ) {
00305             $bitmask = LogPage::DELETED_ACTION;
00306         } elseif ( !$user->isAllowed( 'suppressrevision' ) ) {
00307             $bitmask = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
00308         } else {
00309             $bitmask = 0;
00310         }
00311         if ( $bitmask ) {
00312             $conds[] = $dbr->makeList( array(
00313                 'rc_type != ' . RC_LOG,
00314                 $dbr->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask",
00315             ), LIST_OR );
00316         }
00317 
00318         ChangeTags::modifyDisplayQuery( $tables, $fields, $conds, $join_conds, $options, '' );
00319         wfRunHooks( 'SpecialWatchlistQuery', array( &$conds, &$tables, &$join_conds, &$fields, $values ) );
00320 
00321         $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $join_conds );
00322         $numRows = $res->numRows();
00323 
00324         /* Start bottom header */
00325 
00326         $lang = $this->getLanguage();
00327         $wlInfo = '';
00328         if ( $values['days'] > 0 ) {
00329             $timestamp = wfTimestampNow();
00330             $wlInfo = $this->msg( 'wlnote' )->numParams( $numRows, round( $values['days'] * 24 ) )->params(
00331                 $lang->userDate( $timestamp, $user ), $lang->userTime( $timestamp, $user ) )->parse() . "<br />\n";
00332         }
00333 
00334         $cutofflinks = $this->cutoffLinks( $values['days'], $nondefaults ) . "<br />\n";
00335 
00336         # Spit out some control panel links
00337         $filters = array(
00338             'hideMinor' => 'rcshowhideminor',
00339             'hideBots' => 'rcshowhidebots',
00340             'hideAnons' => 'rcshowhideanons',
00341             'hideLiu' => 'rcshowhideliu',
00342             'hideOwn' => 'rcshowhidemine',
00343             'hidePatrolled' => 'rcshowhidepatr'
00344         );
00345         foreach ( $this->customFilters as $key => $params ) {
00346             $filters[$key] = $params['msg'];
00347         }
00348         // Disable some if needed
00349         if ( !$user->useNPPatrol() ) {
00350             unset( $filters['hidePatrolled'] );
00351         }
00352 
00353         $links = array();
00354         foreach ( $filters as $name => $msg ) {
00355             $links[] = $this->showHideLink( $nondefaults, $msg, $name, $values[$name] );
00356         }
00357 
00358         $hiddenFields = $nondefaults;
00359         unset( $hiddenFields['namespace'] );
00360         unset( $hiddenFields['invert'] );
00361         unset( $hiddenFields['associated'] );
00362 
00363         # Namespace filter and put the whole form together.
00364         $form .= $wlInfo;
00365         $form .= $cutofflinks;
00366         $form .= $lang->pipeList( $links ) . "\n";
00367         $form .= "<hr />\n<p>";
00368         $form .= Html::namespaceSelector(
00369             array(
00370                 'selected' => $nameSpace,
00371                 'all' => '',
00372                 'label' => $this->msg( 'namespace' )->text()
00373             ), array(
00374                 'name' => 'namespace',
00375                 'id' => 'namespace',
00376                 'class' => 'namespaceselector',
00377             )
00378         ) . '&#160;';
00379         $form .= Xml::checkLabel(
00380             $this->msg( 'invert' )->text(),
00381             'invert',
00382             'nsinvert',
00383             $invert,
00384             array( 'title' => $this->msg( 'tooltip-invert' )->text() )
00385         ) . '&#160;';
00386         $form .= Xml::checkLabel(
00387             $this->msg( 'namespace_association' )->text(),
00388             'associated',
00389             'associated',
00390             $associated,
00391             array( 'title' => $this->msg( 'tooltip-namespace_association' )->text() )
00392         ) . '&#160;';
00393         $form .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . "</p>\n";
00394         foreach ( $hiddenFields as $key => $value ) {
00395             $form .= Html::hidden( $key, $value ) . "\n";
00396         }
00397         $form .= Xml::closeElement( 'fieldset' ) . "\n";
00398         $form .= Xml::closeElement( 'form' ) . "\n";
00399         $output->addHTML( $form );
00400 
00401         # If there's nothing to show, stop here
00402         if ( $numRows == 0 ) {
00403             $output->wrapWikiMsg(
00404                 "<div class='mw-changeslist-empty'>\n$1\n</div>", 'recentchanges-noresult'
00405             );
00406             return;
00407         }
00408 
00409         /* End bottom header */
00410 
00411         /* Do link batch query */
00412         $linkBatch = new LinkBatch;
00413         foreach ( $res as $row ) {
00414             $userNameUnderscored = str_replace( ' ', '_', $row->rc_user_text );
00415             if ( $row->rc_user != 0 ) {
00416                 $linkBatch->add( NS_USER, $userNameUnderscored );
00417             }
00418             $linkBatch->add( NS_USER_TALK, $userNameUnderscored );
00419 
00420             $linkBatch->add( $row->rc_namespace, $row->rc_title );
00421         }
00422         $linkBatch->execute();
00423         $dbr->dataSeek( $res, 0 );
00424 
00425         $list = ChangesList::newFromContext( $this->getContext() );
00426         $list->setWatchlistDivs();
00427 
00428         $s = $list->beginRecentChangesList();
00429         $counter = 1;
00430         foreach ( $res as $obj ) {
00431             # Make RC entry
00432             $rc = RecentChange::newFromRow( $obj );
00433             $rc->counter = $counter++;
00434 
00435             if ( $wgShowUpdatedMarker ) {
00436                 $updated = $obj->wl_notificationtimestamp;
00437             } else {
00438                 $updated = false;
00439             }
00440 
00441             if ( $wgRCShowWatchingUsers && $user->getOption( 'shownumberswatching' ) ) {
00442                 $rc->numberofWatchingusers = $dbr->selectField( 'watchlist',
00443                     'COUNT(*)',
00444                     array(
00445                         'wl_namespace' => $obj->rc_namespace,
00446                         'wl_title' => $obj->rc_title,
00447                     ),
00448                     __METHOD__ );
00449             } else {
00450                 $rc->numberofWatchingusers = 0;
00451             }
00452 
00453             $changeLine = $list->recentChangesLine( $rc, $updated, $counter );
00454             if ( $changeLine !== false ) {
00455                 $s .= $changeLine;
00456             }
00457         }
00458         $s .= $list->endRecentChangesList();
00459 
00460         $output->addHTML( $s );
00461     }
00462 
00463     protected function showHideLink( $options, $message, $name, $value ) {
00464         $label = $this->msg( $value ? 'show' : 'hide' )->escaped();
00465         $options[$name] = 1 - (int)$value;
00466 
00467         return $this->msg( $message )->rawParams( Linker::linkKnown( $this->getTitle(), $label, array(), $options ) )->escaped();
00468     }
00469 
00470     protected function hoursLink( $h, $options = array() ) {
00471         $options['days'] = ( $h / 24.0 );
00472 
00473         return Linker::linkKnown(
00474             $this->getTitle(),
00475             $this->getLanguage()->formatNum( $h ),
00476             array(),
00477             $options
00478         );
00479     }
00480 
00481     protected function daysLink( $d, $options = array() ) {
00482         $options['days'] = $d;
00483         $message = ( $d ? $this->getLanguage()->formatNum( $d ) : $this->msg( 'watchlistall2' )->escaped() );
00484 
00485         return Linker::linkKnown(
00486             $this->getTitle(),
00487             $message,
00488             array(),
00489             $options
00490         );
00491     }
00492 
00500     protected function cutoffLinks( $days, $options = array() ) {
00501         $hours = array( 1, 2, 6, 12 );
00502         $days = array( 1, 3, 7 );
00503         $i = 0;
00504         foreach ( $hours as $h ) {
00505             $hours[$i++] = $this->hoursLink( $h, $options );
00506         }
00507         $i = 0;
00508         foreach ( $days as $d ) {
00509             $days[$i++] = $this->daysLink( $d, $options );
00510         }
00511         return $this->msg( 'wlshowlast' )->rawParams(
00512             $this->getLanguage()->pipeList( $hours ),
00513             $this->getLanguage()->pipeList( $days ),
00514             $this->daysLink( 0, $options ) )->parse();
00515     }
00516 
00523     protected function countItems( $dbr ) {
00524         # Fetch the raw count
00525         $res = $dbr->select( 'watchlist', array( 'count' => 'COUNT(*)' ),
00526             array( 'wl_user' => $this->getUser()->getId() ), __METHOD__ );
00527         $row = $dbr->fetchObject( $res );
00528         $count = $row->count;
00529 
00530         return floor( $count / 2 );
00531     }
00532 
00533     protected function getGroupName() {
00534         return 'changes';
00535     }
00536 }