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