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