MediaWiki
REL1_23
|
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 ) . ' '; 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 ) . ' '; 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 ) . ' '; 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 }