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