MediaWiki
REL1_19
|
00001 <?php 00029 class SpecialRecentChanges extends IncludableSpecialPage { 00030 var $rcOptions, $rcSubpage; 00031 protected $customFilters; 00032 00033 public function __construct( $name = 'Recentchanges' ) { 00034 parent::__construct( $name ); 00035 } 00036 00042 public function getDefaultOptions() { 00043 $opts = new FormOptions(); 00044 00045 $opts->add( 'days', (int)$this->getUser()->getOption( 'rcdays' ) ); 00046 $opts->add( 'limit', (int)$this->getUser()->getOption( 'rclimit' ) ); 00047 $opts->add( 'from', '' ); 00048 00049 $opts->add( 'hideminor', $this->getUser()->getBoolOption( 'hideminor' ) ); 00050 $opts->add( 'hidebots', true ); 00051 $opts->add( 'hideanons', false ); 00052 $opts->add( 'hideliu', false ); 00053 $opts->add( 'hidepatrolled', $this->getUser()->getBoolOption( 'hidepatrolled' ) ); 00054 $opts->add( 'hidemyself', false ); 00055 00056 $opts->add( 'namespace', '', FormOptions::INTNULL ); 00057 $opts->add( 'invert', false ); 00058 $opts->add( 'associated', false ); 00059 00060 $opts->add( 'categories', '' ); 00061 $opts->add( 'categories_any', false ); 00062 $opts->add( 'tagfilter', '' ); 00063 return $opts; 00064 } 00065 00073 public function setup( $parameters ) { 00074 $opts = $this->getDefaultOptions(); 00075 00076 foreach( $this->getCustomFilters() as $key => $params ) { 00077 $opts->add( $key, $params['default'] ); 00078 } 00079 00080 $opts->fetchValuesFromRequest( $this->getRequest() ); 00081 00082 // Give precedence to subpage syntax 00083 if( $parameters !== null ) { 00084 $this->parseParameters( $parameters, $opts ); 00085 } 00086 00087 $opts->validateIntBounds( 'limit', 0, 5000 ); 00088 return $opts; 00089 } 00090 00096 protected function getCustomFilters() { 00097 if ( $this->customFilters === null ) { 00098 $this->customFilters = array(); 00099 wfRunHooks( 'SpecialRecentChangesFilters', array( $this, &$this->customFilters ) ); 00100 } 00101 return $this->customFilters; 00102 } 00103 00109 public function feedSetup() { 00110 global $wgFeedLimit; 00111 $opts = $this->getDefaultOptions(); 00112 # Feed is cached on limit,hideminor,namespace; other params would randomly not work 00113 $opts->fetchValuesFromRequest( $this->getRequest(), array( 'limit', 'hideminor', 'namespace' ) ); 00114 $opts->validateIntBounds( 'limit', 0, $wgFeedLimit ); 00115 return $opts; 00116 } 00117 00121 public function getOptions() { 00122 if ( $this->rcOptions === null ) { 00123 if ( $this->including() ) { 00124 $isFeed = false; 00125 } else { 00126 $isFeed = (bool)$this->getRequest()->getVal( 'feed' ); 00127 } 00128 $this->rcOptions = $isFeed ? $this->feedSetup() : $this->setup( $this->rcSubpage ); 00129 } 00130 return $this->rcOptions; 00131 } 00132 00133 00139 public function execute( $subpage ) { 00140 $this->rcSubpage = $subpage; 00141 $feedFormat = $this->including() ? null : $this->getRequest()->getVal( 'feed' ); 00142 00143 # 10 seconds server-side caching max 00144 $this->getOutput()->setSquidMaxage( 10 ); 00145 # Check if the client has a cached version 00146 $lastmod = $this->checkLastModified( $feedFormat ); 00147 if( $lastmod === false ) { 00148 return; 00149 } 00150 00151 $opts = $this->getOptions(); 00152 $this->setHeaders(); 00153 $this->outputHeader(); 00154 $this->addRecentChangesJS(); 00155 00156 // Fetch results, prepare a batch link existence check query 00157 $conds = $this->buildMainQueryConds( $opts ); 00158 $rows = $this->doMainQuery( $conds, $opts ); 00159 if( $rows === false ){ 00160 if( !$this->including() ) { 00161 $this->doHeader( $opts ); 00162 } 00163 return; 00164 } 00165 00166 if( !$feedFormat ) { 00167 $batch = new LinkBatch; 00168 foreach( $rows as $row ) { 00169 $batch->add( NS_USER, $row->rc_user_text ); 00170 $batch->add( NS_USER_TALK, $row->rc_user_text ); 00171 $batch->add( $row->rc_namespace, $row->rc_title ); 00172 } 00173 $batch->execute(); 00174 } 00175 if( $feedFormat ) { 00176 list( $changesFeed, $formatter ) = $this->getFeedObject( $feedFormat ); 00177 $changesFeed->execute( $formatter, $rows, $lastmod, $opts ); 00178 } else { 00179 $this->webOutput( $rows, $opts ); 00180 } 00181 00182 $rows->free(); 00183 } 00184 00190 public function getFeedObject( $feedFormat ){ 00191 $changesFeed = new ChangesFeed( $feedFormat, 'rcfeed' ); 00192 $formatter = $changesFeed->getFeedObject( 00193 wfMsgForContent( 'recentchanges' ), 00194 wfMsgForContent( 'recentchanges-feed-description' ), 00195 $this->getTitle()->getFullURL() 00196 ); 00197 return array( $changesFeed, $formatter ); 00198 } 00199 00207 public function parseParameters( $par, FormOptions $opts ) { 00208 $bits = preg_split( '/\s*,\s*/', trim( $par ) ); 00209 foreach( $bits as $bit ) { 00210 if( 'hidebots' === $bit ) { 00211 $opts['hidebots'] = true; 00212 } 00213 if( 'bots' === $bit ) { 00214 $opts['hidebots'] = false; 00215 } 00216 if( 'hideminor' === $bit ) { 00217 $opts['hideminor'] = true; 00218 } 00219 if( 'minor' === $bit ) { 00220 $opts['hideminor'] = false; 00221 } 00222 if( 'hideliu' === $bit ) { 00223 $opts['hideliu'] = true; 00224 } 00225 if( 'hidepatrolled' === $bit ) { 00226 $opts['hidepatrolled'] = true; 00227 } 00228 if( 'hideanons' === $bit ) { 00229 $opts['hideanons'] = true; 00230 } 00231 if( 'hidemyself' === $bit ) { 00232 $opts['hidemyself'] = true; 00233 } 00234 00235 if( is_numeric( $bit ) ) { 00236 $opts['limit'] = $bit; 00237 } 00238 00239 $m = array(); 00240 if( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) { 00241 $opts['limit'] = $m[1]; 00242 } 00243 if( preg_match( '/^days=(\d+)$/', $bit, $m ) ) { 00244 $opts['days'] = $m[1]; 00245 } 00246 if( preg_match( '/^namespace=(\d+)$/', $bit, $m ) ) { 00247 $opts['namespace'] = $m[1]; 00248 } 00249 } 00250 } 00251 00260 public function checkLastModified( $feedFormat ) { 00261 $dbr = wfGetDB( DB_SLAVE ); 00262 $lastmod = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', false, __METHOD__ ); 00263 if( $feedFormat || !$this->getUser()->useRCPatrol() ) { 00264 if( $lastmod && $this->getOutput()->checkLastModified( $lastmod ) ) { 00265 # Client cache fresh and headers sent, nothing more to do. 00266 return false; 00267 } 00268 } 00269 return $lastmod; 00270 } 00271 00278 public function buildMainQueryConds( FormOptions $opts ) { 00279 $dbr = wfGetDB( DB_SLAVE ); 00280 $conds = array(); 00281 00282 # It makes no sense to hide both anons and logged-in users 00283 # Where this occurs, force anons to be shown 00284 $forcebot = false; 00285 if( $opts['hideanons'] && $opts['hideliu'] ){ 00286 # Check if the user wants to show bots only 00287 if( $opts['hidebots'] ){ 00288 $opts['hideanons'] = false; 00289 } else { 00290 $forcebot = true; 00291 $opts['hidebots'] = false; 00292 } 00293 } 00294 00295 // Calculate cutoff 00296 $cutoff_unixtime = time() - ( $opts['days'] * 86400 ); 00297 $cutoff_unixtime = $cutoff_unixtime - ($cutoff_unixtime % 86400); 00298 $cutoff = $dbr->timestamp( $cutoff_unixtime ); 00299 00300 $fromValid = preg_match('/^[0-9]{14}$/', $opts['from']); 00301 if( $fromValid && $opts['from'] > wfTimestamp(TS_MW,$cutoff) ) { 00302 $cutoff = $dbr->timestamp($opts['from']); 00303 } else { 00304 $opts->reset( 'from' ); 00305 } 00306 00307 $conds[] = 'rc_timestamp >= ' . $dbr->addQuotes( $cutoff ); 00308 00309 $hidePatrol = $this->getUser()->useRCPatrol() && $opts['hidepatrolled']; 00310 $hideLoggedInUsers = $opts['hideliu'] && !$forcebot; 00311 $hideAnonymousUsers = $opts['hideanons'] && !$forcebot; 00312 00313 if( $opts['hideminor'] ) { 00314 $conds['rc_minor'] = 0; 00315 } 00316 if( $opts['hidebots'] ) { 00317 $conds['rc_bot'] = 0; 00318 } 00319 if( $hidePatrol ) { 00320 $conds['rc_patrolled'] = 0; 00321 } 00322 if( $forcebot ) { 00323 $conds['rc_bot'] = 1; 00324 } 00325 if( $hideLoggedInUsers ) { 00326 $conds[] = 'rc_user = 0'; 00327 } 00328 if( $hideAnonymousUsers ) { 00329 $conds[] = 'rc_user != 0'; 00330 } 00331 00332 if( $opts['hidemyself'] ) { 00333 if( $this->getUser()->getId() ) { 00334 $conds[] = 'rc_user != ' . $dbr->addQuotes( $this->getUser()->getId() ); 00335 } else { 00336 $conds[] = 'rc_user_text != ' . $dbr->addQuotes( $this->getUser()->getName() ); 00337 } 00338 } 00339 00340 # Namespace filtering 00341 if( $opts['namespace'] !== '' ) { 00342 $selectedNS = $dbr->addQuotes( $opts['namespace'] ); 00343 $operator = $opts['invert'] ? '!=' : '='; 00344 $boolean = $opts['invert'] ? 'AND' : 'OR'; 00345 00346 # namespace association (bug 2429) 00347 if( !$opts['associated'] ) { 00348 $condition = "rc_namespace $operator $selectedNS"; 00349 } else { 00350 # Also add the associated namespace 00351 $associatedNS = $dbr->addQuotes( 00352 MWNamespace::getAssociated( $opts['namespace'] ) 00353 ); 00354 $condition = "(rc_namespace $operator $selectedNS " 00355 . $boolean 00356 . " rc_namespace $operator $associatedNS)"; 00357 } 00358 00359 $conds[] = $condition; 00360 } 00361 return $conds; 00362 } 00363 00371 public function doMainQuery( $conds, $opts ) { 00372 $tables = array( 'recentchanges' ); 00373 $join_conds = array(); 00374 $query_options = array( 00375 'USE INDEX' => array( 'recentchanges' => 'rc_timestamp' ) 00376 ); 00377 00378 $uid = $this->getUser()->getId(); 00379 $dbr = wfGetDB( DB_SLAVE ); 00380 $limit = $opts['limit']; 00381 $namespace = $opts['namespace']; 00382 $invert = $opts['invert']; 00383 $associated = $opts['associated']; 00384 00385 $fields = array( $dbr->tableName( 'recentchanges' ) . '.*' ); // all rc columns 00386 // JOIN on watchlist for users 00387 if ( $uid ) { 00388 $tables[] = 'watchlist'; 00389 $fields[] = 'wl_user'; 00390 $fields[] = 'wl_notificationtimestamp'; 00391 $join_conds['watchlist'] = array('LEFT JOIN', 00392 "wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace"); 00393 } 00394 if ( $this->getUser()->isAllowed( 'rollback' ) ) { 00395 $tables[] = 'page'; 00396 $fields[] = 'page_latest'; 00397 $join_conds['page'] = array('LEFT JOIN', 'rc_cur_id=page_id'); 00398 } 00399 if ( !$this->including() ) { 00400 // Tag stuff. 00401 // Doesn't work when transcluding. See bug 23293 00402 ChangeTags::modifyDisplayQuery( 00403 $tables, $fields, $conds, $join_conds, $query_options, 00404 $opts['tagfilter'] 00405 ); 00406 } 00407 00408 if ( !wfRunHooks( 'SpecialRecentChangesQuery', 00409 array( &$conds, &$tables, &$join_conds, $opts, &$query_options, &$fields ) ) ) 00410 { 00411 return false; 00412 } 00413 00414 // Don't use the new_namespace_time timestamp index if: 00415 // (a) "All namespaces" selected 00416 // (b) We want pages in more than one namespace (inverted/associated) 00417 // (c) There is a tag to filter on (use tag index instead) 00418 // (d) UNION + sort/limit is not an option for the DBMS 00419 if( $namespace === '' 00420 || ( $invert || $associated ) 00421 || $opts['tagfilter'] != '' 00422 || !$dbr->unionSupportsOrderAndLimit() ) 00423 { 00424 $res = $dbr->select( $tables, $fields, $conds, __METHOD__, 00425 array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit ) + 00426 $query_options, 00427 $join_conds ); 00428 // We have a new_namespace_time index! UNION over new=(0,1) and sort result set! 00429 } else { 00430 // New pages 00431 $sqlNew = $dbr->selectSQLText( 00432 $tables, 00433 $fields, 00434 array( 'rc_new' => 1 ) + $conds, 00435 __METHOD__, 00436 array( 00437 'ORDER BY' => 'rc_timestamp DESC', 00438 'LIMIT' => $limit, 00439 'USE INDEX' => array( 'recentchanges' => 'new_name_timestamp' ) 00440 ), 00441 $join_conds 00442 ); 00443 // Old pages 00444 $sqlOld = $dbr->selectSQLText( 00445 $tables, 00446 $fields, 00447 array( 'rc_new' => 0 ) + $conds, 00448 __METHOD__, 00449 array( 00450 'ORDER BY' => 'rc_timestamp DESC', 00451 'LIMIT' => $limit, 00452 'USE INDEX' => array( 'recentchanges' => 'new_name_timestamp' ) 00453 ), 00454 $join_conds 00455 ); 00456 # Join the two fast queries, and sort the result set 00457 $sql = $dbr->unionQueries( array( $sqlNew, $sqlOld ), false ) . 00458 ' ORDER BY rc_timestamp DESC'; 00459 $sql = $dbr->limitResult( $sql, $limit, false ); 00460 $res = $dbr->query( $sql, __METHOD__ ); 00461 } 00462 00463 return $res; 00464 } 00465 00472 public function webOutput( $rows, $opts ) { 00473 global $wgRCShowWatchingUsers, $wgShowUpdatedMarker, $wgAllowCategorizedRecentChanges; 00474 00475 $limit = $opts['limit']; 00476 00477 if( !$this->including() ) { 00478 // Output options box 00479 $this->doHeader( $opts ); 00480 } 00481 00482 // And now for the content 00483 $this->getOutput()->setFeedAppendQuery( $this->getFeedQuery() ); 00484 00485 if( $wgAllowCategorizedRecentChanges ) { 00486 $this->filterByCategories( $rows, $opts ); 00487 } 00488 00489 $showWatcherCount = $wgRCShowWatchingUsers && $this->getUser()->getOption( 'shownumberswatching' ); 00490 $watcherCache = array(); 00491 00492 $dbr = wfGetDB( DB_SLAVE ); 00493 00494 $counter = 1; 00495 $list = ChangesList::newFromContext( $this->getContext() ); 00496 00497 $s = $list->beginRecentChangesList(); 00498 foreach( $rows as $obj ) { 00499 if( $limit == 0 ) { 00500 break; 00501 } 00502 $rc = RecentChange::newFromRow( $obj ); 00503 $rc->counter = $counter++; 00504 # Check if the page has been updated since the last visit 00505 if( $wgShowUpdatedMarker && !empty( $obj->wl_notificationtimestamp ) ) { 00506 $rc->notificationtimestamp = ( $obj->rc_timestamp >= $obj->wl_notificationtimestamp ); 00507 } else { 00508 $rc->notificationtimestamp = false; // Default 00509 } 00510 # Check the number of users watching the page 00511 $rc->numberofWatchingusers = 0; // Default 00512 if( $showWatcherCount && $obj->rc_namespace >= 0 ) { 00513 if( !isset( $watcherCache[$obj->rc_namespace][$obj->rc_title] ) ) { 00514 $watcherCache[$obj->rc_namespace][$obj->rc_title] = 00515 $dbr->selectField( 00516 'watchlist', 00517 'COUNT(*)', 00518 array( 00519 'wl_namespace' => $obj->rc_namespace, 00520 'wl_title' => $obj->rc_title, 00521 ), 00522 __METHOD__ . '-watchers' 00523 ); 00524 } 00525 $rc->numberofWatchingusers = $watcherCache[$obj->rc_namespace][$obj->rc_title]; 00526 } 00527 $s .= $list->recentChangesLine( $rc, !empty( $obj->wl_user ), $counter ); 00528 --$limit; 00529 } 00530 $s .= $list->endRecentChangesList(); 00531 $this->getOutput()->addHTML( $s ); 00532 } 00533 00538 public function getFeedQuery() { 00539 return false; 00540 } 00541 00548 public function doHeader( $opts ) { 00549 global $wgScript; 00550 00551 $this->setTopText( $opts ); 00552 00553 $defaults = $opts->getAllValues(); 00554 $nondefaults = $opts->getChangedValues(); 00555 $opts->consumeValues( array( 00556 'namespace', 'invert', 'associated', 'tagfilter', 00557 'categories', 'categories_any' 00558 ) ); 00559 00560 $panel = array(); 00561 $panel[] = $this->optionsPanel( $defaults, $nondefaults ); 00562 $panel[] = '<hr />'; 00563 00564 $extraOpts = $this->getExtraOptions( $opts ); 00565 $extraOptsCount = count( $extraOpts ); 00566 $count = 0; 00567 $submit = ' ' . Xml::submitbutton( wfMsg( 'allpagessubmit' ) ); 00568 00569 $out = Xml::openElement( 'table', array( 'class' => 'mw-recentchanges-table' ) ); 00570 foreach( $extraOpts as $optionRow ) { 00571 # Add submit button to the last row only 00572 ++$count; 00573 $addSubmit = $count === $extraOptsCount ? $submit : ''; 00574 00575 $out .= Xml::openElement( 'tr' ); 00576 if( is_array( $optionRow ) ) { 00577 $out .= Xml::tags( 'td', array( 'class' => 'mw-label' ), $optionRow[0] ); 00578 $out .= Xml::tags( 'td', array( 'class' => 'mw-input' ), $optionRow[1] . $addSubmit ); 00579 } else { 00580 $out .= Xml::tags( 'td', array( 'class' => 'mw-input', 'colspan' => 2 ), $optionRow . $addSubmit ); 00581 } 00582 $out .= Xml::closeElement( 'tr' ); 00583 } 00584 $out .= Xml::closeElement( 'table' ); 00585 00586 $unconsumed = $opts->getUnconsumedValues(); 00587 foreach( $unconsumed as $key => $value ) { 00588 $out .= Html::hidden( $key, $value ); 00589 } 00590 00591 $t = $this->getTitle(); 00592 $out .= Html::hidden( 'title', $t->getPrefixedText() ); 00593 $form = Xml::tags( 'form', array( 'action' => $wgScript ), $out ); 00594 $panel[] = $form; 00595 $panelString = implode( "\n", $panel ); 00596 00597 $this->getOutput()->addHTML( 00598 Xml::fieldset( wfMsg( 'recentchanges-legend' ), $panelString, array( 'class' => 'rcoptions' ) ) 00599 ); 00600 00601 $this->setBottomText( $opts ); 00602 } 00603 00610 function getExtraOptions( $opts ) { 00611 $extraOpts = array(); 00612 $extraOpts['namespace'] = $this->namespaceFilterForm( $opts ); 00613 00614 global $wgAllowCategorizedRecentChanges; 00615 if( $wgAllowCategorizedRecentChanges ) { 00616 $extraOpts['category'] = $this->categoryFilterForm( $opts ); 00617 } 00618 00619 $tagFilter = ChangeTags::buildTagFilterSelector( $opts['tagfilter'] ); 00620 if ( count( $tagFilter ) ) { 00621 $extraOpts['tagfilter'] = $tagFilter; 00622 } 00623 00624 wfRunHooks( 'SpecialRecentChangesPanel', array( &$extraOpts, $opts ) ); 00625 return $extraOpts; 00626 } 00627 00633 function setTopText( FormOptions $opts ) { 00634 global $wgContLang; 00635 $this->getOutput()->addWikiText( 00636 Html::rawElement( 'p', 00637 array( 'lang' => $wgContLang->getCode(), 'dir' => $wgContLang->getDir() ), 00638 "\n" . wfMsgForContentNoTrans( 'recentchangestext' ) . "\n" 00639 ), 00640 /* $lineStart */ false, 00641 /* $interface */ false 00642 ); 00643 } 00644 00651 function setBottomText( FormOptions $opts ) {} 00652 00660 protected function namespaceFilterForm( FormOptions $opts ) { 00661 $nsSelect = Html::namespaceSelector( 00662 array( 'selected' => $opts['namespace'], 'all' => '' ), 00663 array( 'name' => 'namespace', 'id' => 'namespace' ) 00664 ); 00665 $nsLabel = Xml::label( wfMsg( 'namespace' ), 'namespace' ); 00666 $invert = Xml::checkLabel( 00667 wfMsg( 'invert' ), 'invert', 'nsinvert', 00668 $opts['invert'], 00669 array( 'title' => wfMsg( 'tooltip-invert' ) ) 00670 ); 00671 $associated = Xml::checkLabel( 00672 wfMsg( 'namespace_association' ), 'associated', 'nsassociated', 00673 $opts['associated'], 00674 array( 'title' => wfMsg( 'tooltip-namespace_association' ) ) 00675 ); 00676 return array( $nsLabel, "$nsSelect $invert $associated" ); 00677 } 00678 00685 protected function categoryFilterForm( FormOptions $opts ) { 00686 list( $label, $input ) = Xml::inputLabelSep( wfMsg( 'rc_categories' ), 00687 'categories', 'mw-categories', false, $opts['categories'] ); 00688 00689 $input .= ' ' . Xml::checkLabel( wfMsg( 'rc_categories_any' ), 00690 'categories_any', 'mw-categories_any', $opts['categories_any'] ); 00691 00692 return array( $label, $input ); 00693 } 00694 00701 function filterByCategories( &$rows, FormOptions $opts ) { 00702 $categories = array_map( 'trim', explode( '|' , $opts['categories'] ) ); 00703 00704 if( !count( $categories ) ) { 00705 return; 00706 } 00707 00708 # Filter categories 00709 $cats = array(); 00710 foreach( $categories as $cat ) { 00711 $cat = trim( $cat ); 00712 if( $cat == '' ) { 00713 continue; 00714 } 00715 $cats[] = $cat; 00716 } 00717 00718 # Filter articles 00719 $articles = array(); 00720 $a2r = array(); 00721 $rowsarr = array(); 00722 foreach( $rows as $k => $r ) { 00723 $nt = Title::makeTitle( $r->rc_namespace, $r->rc_title ); 00724 $id = $nt->getArticleID(); 00725 if( $id == 0 ) { 00726 continue; # Page might have been deleted... 00727 } 00728 if( !in_array( $id, $articles ) ) { 00729 $articles[] = $id; 00730 } 00731 if( !isset( $a2r[$id] ) ) { 00732 $a2r[$id] = array(); 00733 } 00734 $a2r[$id][] = $k; 00735 $rowsarr[$k] = $r; 00736 } 00737 00738 # Shortcut? 00739 if( !count( $articles ) || !count( $cats ) ) { 00740 return; 00741 } 00742 00743 # Look up 00744 $c = new Categoryfinder; 00745 $c->seed( $articles, $cats, $opts['categories_any'] ? 'OR' : 'AND' ); 00746 $match = $c->run(); 00747 00748 # Filter 00749 $newrows = array(); 00750 foreach( $match as $id ) { 00751 foreach( $a2r[$id] as $rev ) { 00752 $k = $rev; 00753 $newrows[$k] = $rowsarr[$k]; 00754 } 00755 } 00756 $rows = $newrows; 00757 } 00758 00767 function makeOptionsLink( $title, $override, $options, $active = false ) { 00768 $params = $override + $options; 00769 $text = htmlspecialchars( $title ); 00770 if ( $active ) { 00771 $text = '<strong>' . $text . '</strong>'; 00772 } 00773 return Linker::linkKnown( $this->getTitle(), $text, array(), $params ); 00774 } 00775 00782 function optionsPanel( $defaults, $nondefaults ) { 00783 global $wgRCLinkLimits, $wgRCLinkDays; 00784 00785 $options = $nondefaults + $defaults; 00786 00787 $note = ''; 00788 if( !wfEmptyMsg( 'rclegend' ) ) { 00789 $note .= '<div class="mw-rclegend">' . 00790 wfMsgExt( 'rclegend', array( 'parseinline' ) ) . "</div>\n"; 00791 } 00792 if( $options['from'] ) { 00793 $note .= wfMsgExt( 'rcnotefrom', array( 'parseinline' ), 00794 $this->getLanguage()->formatNum( $options['limit'] ), 00795 $this->getLanguage()->timeanddate( $options['from'], true ), 00796 $this->getLanguage()->date( $options['from'], true ), 00797 $this->getLanguage()->time( $options['from'], true ) ) . '<br />'; 00798 } 00799 00800 # Sort data for display and make sure it's unique after we've added user data. 00801 $wgRCLinkLimits[] = $options['limit']; 00802 $wgRCLinkDays[] = $options['days']; 00803 sort( $wgRCLinkLimits ); 00804 sort( $wgRCLinkDays ); 00805 $wgRCLinkLimits = array_unique( $wgRCLinkLimits ); 00806 $wgRCLinkDays = array_unique( $wgRCLinkDays ); 00807 00808 // limit links 00809 foreach( $wgRCLinkLimits as $value ) { 00810 $cl[] = $this->makeOptionsLink( $this->getLanguage()->formatNum( $value ), 00811 array( 'limit' => $value ), $nondefaults, $value == $options['limit'] ); 00812 } 00813 $cl = $this->getLanguage()->pipeList( $cl ); 00814 00815 // day links, reset 'from' to none 00816 foreach( $wgRCLinkDays as $value ) { 00817 $dl[] = $this->makeOptionsLink( $this->getLanguage()->formatNum( $value ), 00818 array( 'days' => $value, 'from' => '' ), $nondefaults, $value == $options['days'] ); 00819 } 00820 $dl = $this->getLanguage()->pipeList( $dl ); 00821 00822 00823 // show/hide links 00824 $showhide = array( wfMsg( 'show' ), wfMsg( 'hide' ) ); 00825 $filters = array( 00826 'hideminor' => 'rcshowhideminor', 00827 'hidebots' => 'rcshowhidebots', 00828 'hideanons' => 'rcshowhideanons', 00829 'hideliu' => 'rcshowhideliu', 00830 'hidepatrolled' => 'rcshowhidepatr', 00831 'hidemyself' => 'rcshowhidemine' 00832 ); 00833 foreach ( $this->getCustomFilters() as $key => $params ) { 00834 $filters[$key] = $params['msg']; 00835 } 00836 // Disable some if needed 00837 if ( !$this->getUser()->useRCPatrol() ) { 00838 unset( $filters['hidepatrolled'] ); 00839 } 00840 00841 $links = array(); 00842 foreach ( $filters as $key => $msg ) { 00843 $link = $this->makeOptionsLink( $showhide[1 - $options[$key]], 00844 array( $key => 1-$options[$key] ), $nondefaults ); 00845 $links[] = wfMsgHtml( $msg, $link ); 00846 } 00847 00848 // show from this onward link 00849 $timestamp = wfTimestampNow(); 00850 $now = $this->getLanguage()->timeanddate( $timestamp, true ); 00851 $tl = $this->makeOptionsLink( 00852 $now, array( 'from' => $timestamp ), $nondefaults 00853 ); 00854 00855 $rclinks = wfMsgExt( 'rclinks', array( 'parseinline', 'replaceafter' ), 00856 $cl, $dl, $this->getLanguage()->pipeList( $links ) ); 00857 $rclistfrom = wfMsgExt( 'rclistfrom', array( 'parseinline', 'replaceafter' ), $tl ); 00858 return "{$note}$rclinks<br />$rclistfrom"; 00859 } 00860 00864 function addRecentChangesJS() { 00865 $this->getOutput()->addModules( array( 00866 'mediawiki.special.recentchanges', 00867 ) ); 00868 } 00869 }