MediaWiki
REL1_22
|
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 $user = $this->getUser(); 00045 00046 $opts->add( 'days', $user->getIntOption( 'rcdays' ) ); 00047 $opts->add( 'limit', $user->getIntOption( 'rclimit' ) ); 00048 $opts->add( 'from', '' ); 00049 00050 $opts->add( 'hideminor', $user->getBoolOption( 'hideminor' ) ); 00051 $opts->add( 'hidebots', true ); 00052 $opts->add( 'hideanons', false ); 00053 $opts->add( 'hideliu', false ); 00054 $opts->add( 'hidepatrolled', $user->getBoolOption( 'hidepatrolled' ) ); 00055 $opts->add( 'hidemyself', false ); 00056 00057 $opts->add( 'namespace', '', FormOptions::INTNULL ); 00058 $opts->add( 'invert', false ); 00059 $opts->add( 'associated', false ); 00060 00061 $opts->add( 'categories', '' ); 00062 $opts->add( 'categories_any', false ); 00063 $opts->add( 'tagfilter', '' ); 00064 00065 return $opts; 00066 } 00067 00075 public function setup( $parameters ) { 00076 $opts = $this->getDefaultOptions(); 00077 00078 foreach ( $this->getCustomFilters() as $key => $params ) { 00079 $opts->add( $key, $params['default'] ); 00080 } 00081 00082 $opts->fetchValuesFromRequest( $this->getRequest() ); 00083 00084 // Give precedence to subpage syntax 00085 if ( $parameters !== null ) { 00086 $this->parseParameters( $parameters, $opts ); 00087 } 00088 00089 $opts->validateIntBounds( 'limit', 0, 5000 ); 00090 00091 return $opts; 00092 } 00093 00099 protected function getCustomFilters() { 00100 if ( $this->customFilters === null ) { 00101 $this->customFilters = array(); 00102 wfRunHooks( 'SpecialRecentChangesFilters', array( $this, &$this->customFilters ) ); 00103 } 00104 00105 return $this->customFilters; 00106 } 00107 00113 public function feedSetup() { 00114 global $wgFeedLimit; 00115 $opts = $this->getDefaultOptions(); 00116 $opts->fetchValuesFromRequest( $this->getRequest() ); 00117 $opts->validateIntBounds( 'limit', 0, $wgFeedLimit ); 00118 00119 return $opts; 00120 } 00121 00125 public function getOptions() { 00126 if ( $this->rcOptions === null ) { 00127 if ( $this->including() ) { 00128 $isFeed = false; 00129 } else { 00130 $isFeed = (bool)$this->getRequest()->getVal( 'feed' ); 00131 } 00132 $this->rcOptions = $isFeed ? $this->feedSetup() : $this->setup( $this->rcSubpage ); 00133 } 00134 00135 return $this->rcOptions; 00136 } 00137 00143 public function execute( $subpage ) { 00144 $this->rcSubpage = $subpage; 00145 $feedFormat = $this->including() ? null : $this->getRequest()->getVal( 'feed' ); 00146 00147 # 10 seconds server-side caching max 00148 $this->getOutput()->setSquidMaxage( 10 ); 00149 # Check if the client has a cached version 00150 $lastmod = $this->checkLastModified( $feedFormat ); 00151 if ( $lastmod === false ) { 00152 return; 00153 } 00154 00155 $opts = $this->getOptions(); 00156 $this->setHeaders(); 00157 $this->outputHeader(); 00158 $this->addModules(); 00159 00160 // Fetch results, prepare a batch link existence check query 00161 $conds = $this->buildMainQueryConds( $opts ); 00162 $rows = $this->doMainQuery( $conds, $opts ); 00163 if ( $rows === false ) { 00164 if ( !$this->including() ) { 00165 $this->doHeader( $opts ); 00166 } 00167 00168 return; 00169 } 00170 00171 if ( !$feedFormat ) { 00172 $batch = new LinkBatch; 00173 foreach ( $rows as $row ) { 00174 $batch->add( NS_USER, $row->rc_user_text ); 00175 $batch->add( NS_USER_TALK, $row->rc_user_text ); 00176 $batch->add( $row->rc_namespace, $row->rc_title ); 00177 } 00178 $batch->execute(); 00179 } 00180 if ( $feedFormat ) { 00181 list( $changesFeed, $formatter ) = $this->getFeedObject( $feedFormat ); 00183 $changesFeed->execute( $formatter, $rows, $lastmod, $opts ); 00184 } else { 00185 $this->webOutput( $rows, $opts ); 00186 } 00187 00188 $rows->free(); 00189 } 00190 00197 public function getFeedObject( $feedFormat ) { 00198 $changesFeed = new ChangesFeed( $feedFormat, 'rcfeed' ); 00199 $formatter = $changesFeed->getFeedObject( 00200 $this->msg( 'recentchanges' )->inContentLanguage()->text(), 00201 $this->msg( 'recentchanges-feed-description' )->inContentLanguage()->text(), 00202 $this->getTitle()->getFullURL() 00203 ); 00204 00205 return array( $changesFeed, $formatter ); 00206 } 00207 00215 public function parseParameters( $par, FormOptions $opts ) { 00216 $bits = preg_split( '/\s*,\s*/', trim( $par ) ); 00217 foreach ( $bits as $bit ) { 00218 if ( 'hidebots' === $bit ) { 00219 $opts['hidebots'] = true; 00220 } 00221 if ( 'bots' === $bit ) { 00222 $opts['hidebots'] = false; 00223 } 00224 if ( 'hideminor' === $bit ) { 00225 $opts['hideminor'] = true; 00226 } 00227 if ( 'minor' === $bit ) { 00228 $opts['hideminor'] = false; 00229 } 00230 if ( 'hideliu' === $bit ) { 00231 $opts['hideliu'] = true; 00232 } 00233 if ( 'hidepatrolled' === $bit ) { 00234 $opts['hidepatrolled'] = true; 00235 } 00236 if ( 'hideanons' === $bit ) { 00237 $opts['hideanons'] = true; 00238 } 00239 if ( 'hidemyself' === $bit ) { 00240 $opts['hidemyself'] = true; 00241 } 00242 00243 if ( is_numeric( $bit ) ) { 00244 $opts['limit'] = $bit; 00245 } 00246 00247 $m = array(); 00248 if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) { 00249 $opts['limit'] = $m[1]; 00250 } 00251 if ( preg_match( '/^days=(\d+)$/', $bit, $m ) ) { 00252 $opts['days'] = $m[1]; 00253 } 00254 if ( preg_match( '/^namespace=(\d+)$/', $bit, $m ) ) { 00255 $opts['namespace'] = $m[1]; 00256 } 00257 } 00258 } 00259 00268 public function checkLastModified( $feedFormat ) { 00269 $dbr = wfGetDB( DB_SLAVE ); 00270 $lastmod = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', false, __METHOD__ ); 00271 if ( $feedFormat || !$this->getUser()->useRCPatrol() ) { 00272 if ( $lastmod && $this->getOutput()->checkLastModified( $lastmod ) ) { 00273 # Client cache fresh and headers sent, nothing more to do. 00274 return false; 00275 } 00276 } 00277 00278 return $lastmod; 00279 } 00280 00287 public function buildMainQueryConds( FormOptions $opts ) { 00288 $dbr = wfGetDB( DB_SLAVE ); 00289 $conds = array(); 00290 00291 # It makes no sense to hide both anons and logged-in users 00292 # Where this occurs, force anons to be shown 00293 $forcebot = false; 00294 if ( $opts['hideanons'] && $opts['hideliu'] ) { 00295 # Check if the user wants to show bots only 00296 if ( $opts['hidebots'] ) { 00297 $opts['hideanons'] = false; 00298 } else { 00299 $forcebot = true; 00300 $opts['hidebots'] = false; 00301 } 00302 } 00303 00304 // Calculate cutoff 00305 $cutoff_unixtime = time() - ( $opts['days'] * 86400 ); 00306 $cutoff_unixtime = $cutoff_unixtime - ( $cutoff_unixtime % 86400 ); 00307 $cutoff = $dbr->timestamp( $cutoff_unixtime ); 00308 00309 $fromValid = preg_match( '/^[0-9]{14}$/', $opts['from'] ); 00310 if ( $fromValid && $opts['from'] > wfTimestamp( TS_MW, $cutoff ) ) { 00311 $cutoff = $dbr->timestamp( $opts['from'] ); 00312 } else { 00313 $opts->reset( 'from' ); 00314 } 00315 00316 $conds[] = 'rc_timestamp >= ' . $dbr->addQuotes( $cutoff ); 00317 00318 $hidePatrol = $this->getUser()->useRCPatrol() && $opts['hidepatrolled']; 00319 $hideLoggedInUsers = $opts['hideliu'] && !$forcebot; 00320 $hideAnonymousUsers = $opts['hideanons'] && !$forcebot; 00321 00322 if ( $opts['hideminor'] ) { 00323 $conds['rc_minor'] = 0; 00324 } 00325 if ( $opts['hidebots'] ) { 00326 $conds['rc_bot'] = 0; 00327 } 00328 if ( $hidePatrol ) { 00329 $conds['rc_patrolled'] = 0; 00330 } 00331 if ( $forcebot ) { 00332 $conds['rc_bot'] = 1; 00333 } 00334 if ( $hideLoggedInUsers ) { 00335 $conds[] = 'rc_user = 0'; 00336 } 00337 if ( $hideAnonymousUsers ) { 00338 $conds[] = 'rc_user != 0'; 00339 } 00340 00341 if ( $opts['hidemyself'] ) { 00342 if ( $this->getUser()->getId() ) { 00343 $conds[] = 'rc_user != ' . $dbr->addQuotes( $this->getUser()->getId() ); 00344 } else { 00345 $conds[] = 'rc_user_text != ' . $dbr->addQuotes( $this->getUser()->getName() ); 00346 } 00347 } 00348 00349 # Namespace filtering 00350 if ( $opts['namespace'] !== '' ) { 00351 $selectedNS = $dbr->addQuotes( $opts['namespace'] ); 00352 $operator = $opts['invert'] ? '!=' : '='; 00353 $boolean = $opts['invert'] ? 'AND' : 'OR'; 00354 00355 # namespace association (bug 2429) 00356 if ( !$opts['associated'] ) { 00357 $condition = "rc_namespace $operator $selectedNS"; 00358 } else { 00359 # Also add the associated namespace 00360 $associatedNS = $dbr->addQuotes( 00361 MWNamespace::getAssociated( $opts['namespace'] ) 00362 ); 00363 $condition = "(rc_namespace $operator $selectedNS " 00364 . $boolean 00365 . " rc_namespace $operator $associatedNS)"; 00366 } 00367 00368 $conds[] = $condition; 00369 } 00370 00371 return $conds; 00372 } 00373 00381 public function doMainQuery( $conds, $opts ) { 00382 $tables = array( 'recentchanges' ); 00383 $join_conds = array(); 00384 $query_options = array( 00385 'USE INDEX' => array( 'recentchanges' => 'rc_timestamp' ) 00386 ); 00387 00388 $uid = $this->getUser()->getId(); 00389 $dbr = wfGetDB( DB_SLAVE ); 00390 $limit = $opts['limit']; 00391 $namespace = $opts['namespace']; 00392 $invert = $opts['invert']; 00393 $associated = $opts['associated']; 00394 00395 $fields = RecentChange::selectFields(); 00396 // JOIN on watchlist for users 00397 if ( $uid && $this->getUser()->isAllowed( 'viewmywatchlist' ) ) { 00398 $tables[] = 'watchlist'; 00399 $fields[] = 'wl_user'; 00400 $fields[] = 'wl_notificationtimestamp'; 00401 $join_conds['watchlist'] = array( 'LEFT JOIN', array( 00402 'wl_user' => $uid, 00403 'wl_title=rc_title', 00404 'wl_namespace=rc_namespace' 00405 ) ); 00406 } 00407 if ( $this->getUser()->isAllowed( 'rollback' ) ) { 00408 $tables[] = 'page'; 00409 $fields[] = 'page_latest'; 00410 $join_conds['page'] = array( 'LEFT JOIN', 'rc_cur_id=page_id' ); 00411 } 00412 // Tag stuff. 00413 ChangeTags::modifyDisplayQuery( 00414 $tables, 00415 $fields, 00416 $conds, 00417 $join_conds, 00418 $query_options, 00419 $opts['tagfilter'] 00420 ); 00421 00422 if ( !wfRunHooks( 'SpecialRecentChangesQuery', 00423 array( &$conds, &$tables, &$join_conds, $opts, &$query_options, &$fields ) ) 00424 ) { 00425 return false; 00426 } 00427 00428 // rc_new is not an ENUM, but adding a redundant rc_new IN (0,1) gives mysql enough 00429 // knowledge to use an index merge if it wants (it may use some other index though). 00430 return $dbr->select( 00431 $tables, 00432 $fields, 00433 $conds + array( 'rc_new' => array( 0, 1 ) ), 00434 __METHOD__, 00435 array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit ) + $query_options, 00436 $join_conds 00437 ); 00438 } 00439 00446 public function webOutput( $rows, $opts ) { 00447 global $wgRCShowWatchingUsers, $wgShowUpdatedMarker, $wgAllowCategorizedRecentChanges; 00448 00449 // Build the final data 00450 00451 if ( $wgAllowCategorizedRecentChanges ) { 00452 $this->filterByCategories( $rows, $opts ); 00453 } 00454 00455 $limit = $opts['limit']; 00456 00457 $showWatcherCount = $wgRCShowWatchingUsers && $this->getUser()->getOption( 'shownumberswatching' ); 00458 $watcherCache = array(); 00459 00460 $dbr = wfGetDB( DB_SLAVE ); 00461 00462 $counter = 1; 00463 $list = ChangesList::newFromContext( $this->getContext() ); 00464 00465 $rclistOutput = $list->beginRecentChangesList(); 00466 foreach ( $rows as $obj ) { 00467 if ( $limit == 0 ) { 00468 break; 00469 } 00470 $rc = RecentChange::newFromRow( $obj ); 00471 $rc->counter = $counter++; 00472 # Check if the page has been updated since the last visit 00473 if ( $wgShowUpdatedMarker && !empty( $obj->wl_notificationtimestamp ) ) { 00474 $rc->notificationtimestamp = ( $obj->rc_timestamp >= $obj->wl_notificationtimestamp ); 00475 } else { 00476 $rc->notificationtimestamp = false; // Default 00477 } 00478 # Check the number of users watching the page 00479 $rc->numberofWatchingusers = 0; // Default 00480 if ( $showWatcherCount && $obj->rc_namespace >= 0 ) { 00481 if ( !isset( $watcherCache[$obj->rc_namespace][$obj->rc_title] ) ) { 00482 $watcherCache[$obj->rc_namespace][$obj->rc_title] = 00483 $dbr->selectField( 00484 'watchlist', 00485 'COUNT(*)', 00486 array( 00487 'wl_namespace' => $obj->rc_namespace, 00488 'wl_title' => $obj->rc_title, 00489 ), 00490 __METHOD__ . '-watchers' 00491 ); 00492 } 00493 $rc->numberofWatchingusers = $watcherCache[$obj->rc_namespace][$obj->rc_title]; 00494 } 00495 00496 $changeLine = $list->recentChangesLine( $rc, !empty( $obj->wl_user ), $counter ); 00497 if ( $changeLine !== false ) { 00498 $rclistOutput .= $changeLine; 00499 --$limit; 00500 } 00501 } 00502 $rclistOutput .= $list->endRecentChangesList(); 00503 00504 // Print things out 00505 00506 if ( !$this->including() ) { 00507 // Output options box 00508 $this->doHeader( $opts ); 00509 } 00510 00511 // And now for the content 00512 $feedQuery = $this->getFeedQuery(); 00513 if ( $feedQuery !== '' ) { 00514 $this->getOutput()->setFeedAppendQuery( $feedQuery ); 00515 } else { 00516 $this->getOutput()->setFeedAppendQuery( false ); 00517 } 00518 00519 if ( $rows->numRows() === 0 ) { 00520 $this->getOutput()->wrapWikiMsg( 00521 "<div class='mw-changeslist-empty'>\n$1\n</div>", 'recentchanges-noresult' 00522 ); 00523 } else { 00524 $this->getOutput()->addHTML( $rclistOutput ); 00525 } 00526 } 00527 00533 public function getFeedQuery() { 00534 global $wgFeedLimit; 00535 00536 $this->getOptions()->validateIntBounds( 'limit', 0, $wgFeedLimit ); 00537 $options = $this->getOptions()->getChangedValues(); 00538 00539 // wfArrayToCgi() omits options set to null or false 00540 foreach ( $options as &$value ) { 00541 if ( $value === false ) { 00542 $value = '0'; 00543 } 00544 } 00545 unset( $value ); 00546 00547 return wfArrayToCgi( $options ); 00548 } 00549 00556 public function doHeader( $opts ) { 00557 global $wgScript; 00558 00559 $this->setTopText( $opts ); 00560 00561 $defaults = $opts->getAllValues(); 00562 $nondefaults = $opts->getChangedValues(); 00563 00564 $panel = array(); 00565 $panel[] = $this->optionsPanel( $defaults, $nondefaults ); 00566 $panel[] = '<hr />'; 00567 00568 $extraOpts = $this->getExtraOptions( $opts ); 00569 $extraOptsCount = count( $extraOpts ); 00570 $count = 0; 00571 $submit = ' ' . Xml::submitbutton( $this->msg( 'allpagessubmit' )->text() ); 00572 00573 $out = Xml::openElement( 'table', array( 'class' => 'mw-recentchanges-table' ) ); 00574 foreach ( $extraOpts as $name => $optionRow ) { 00575 # Add submit button to the last row only 00576 ++$count; 00577 $addSubmit = ( $count === $extraOptsCount ) ? $submit : ''; 00578 00579 $out .= Xml::openElement( 'tr' ); 00580 if ( is_array( $optionRow ) ) { 00581 $out .= Xml::tags( 00582 'td', 00583 array( 'class' => 'mw-label mw-' . $name . '-label' ), 00584 $optionRow[0] 00585 ); 00586 $out .= Xml::tags( 00587 'td', 00588 array( 'class' => 'mw-input' ), 00589 $optionRow[1] . $addSubmit 00590 ); 00591 } else { 00592 $out .= Xml::tags( 00593 'td', 00594 array( 'class' => 'mw-input', 'colspan' => 2 ), 00595 $optionRow . $addSubmit 00596 ); 00597 } 00598 $out .= Xml::closeElement( 'tr' ); 00599 } 00600 $out .= Xml::closeElement( 'table' ); 00601 00602 $unconsumed = $opts->getUnconsumedValues(); 00603 foreach ( $unconsumed as $key => $value ) { 00604 $out .= Html::hidden( $key, $value ); 00605 } 00606 00607 $t = $this->getTitle(); 00608 $out .= Html::hidden( 'title', $t->getPrefixedText() ); 00609 $form = Xml::tags( 'form', array( 'action' => $wgScript ), $out ); 00610 $panel[] = $form; 00611 $panelString = implode( "\n", $panel ); 00612 00613 $this->getOutput()->addHTML( 00614 Xml::fieldset( 00615 $this->msg( 'recentchanges-legend' )->text(), 00616 $panelString, 00617 array( 'class' => 'rcoptions' ) 00618 ) 00619 ); 00620 00621 $this->setBottomText( $opts ); 00622 } 00623 00630 function getExtraOptions( $opts ) { 00631 $opts->consumeValues( array( 00632 'namespace', 'invert', 'associated', 'tagfilter', 'categories', 'categories_any' 00633 ) ); 00634 00635 $extraOpts = array(); 00636 $extraOpts['namespace'] = $this->namespaceFilterForm( $opts ); 00637 00638 global $wgAllowCategorizedRecentChanges; 00639 if ( $wgAllowCategorizedRecentChanges ) { 00640 $extraOpts['category'] = $this->categoryFilterForm( $opts ); 00641 } 00642 00643 $tagFilter = ChangeTags::buildTagFilterSelector( $opts['tagfilter'] ); 00644 if ( count( $tagFilter ) ) { 00645 $extraOpts['tagfilter'] = $tagFilter; 00646 } 00647 00648 // Don't fire the hook for subclasses. (Or should we?) 00649 if ( $this->getName() === 'Recentchanges' ) { 00650 wfRunHooks( 'SpecialRecentChangesPanel', array( &$extraOpts, $opts ) ); 00651 } 00652 00653 return $extraOpts; 00654 } 00655 00661 function setTopText( FormOptions $opts ) { 00662 global $wgContLang; 00663 00664 $message = $this->msg( 'recentchangestext' )->inContentLanguage(); 00665 if ( !$message->isDisabled() ) { 00666 $this->getOutput()->addWikiText( 00667 Html::rawElement( 'p', 00668 array( 'lang' => $wgContLang->getCode(), 'dir' => $wgContLang->getDir() ), 00669 "\n" . $message->plain() . "\n" 00670 ), 00671 /* $lineStart */ false, 00672 /* $interface */ false 00673 ); 00674 } 00675 } 00676 00682 function setBottomText( FormOptions $opts ) { 00683 } 00684 00692 protected function namespaceFilterForm( FormOptions $opts ) { 00693 $nsSelect = Html::namespaceSelector( 00694 array( 'selected' => $opts['namespace'], 'all' => '' ), 00695 array( 'name' => 'namespace', 'id' => 'namespace' ) 00696 ); 00697 $nsLabel = Xml::label( $this->msg( 'namespace' )->text(), 'namespace' ); 00698 $invert = Xml::checkLabel( 00699 $this->msg( 'invert' )->text(), 'invert', 'nsinvert', 00700 $opts['invert'], 00701 array( 'title' => $this->msg( 'tooltip-invert' )->text() ) 00702 ); 00703 $associated = Xml::checkLabel( 00704 $this->msg( 'namespace_association' )->text(), 'associated', 'nsassociated', 00705 $opts['associated'], 00706 array( 'title' => $this->msg( 'tooltip-namespace_association' )->text() ) 00707 ); 00708 00709 return array( $nsLabel, "$nsSelect $invert $associated" ); 00710 } 00711 00718 protected function categoryFilterForm( FormOptions $opts ) { 00719 list( $label, $input ) = Xml::inputLabelSep( $this->msg( 'rc_categories' )->text(), 00720 'categories', 'mw-categories', false, $opts['categories'] ); 00721 00722 $input .= ' ' . Xml::checkLabel( $this->msg( 'rc_categories_any' )->text(), 00723 'categories_any', 'mw-categories_any', $opts['categories_any'] ); 00724 00725 return array( $label, $input ); 00726 } 00727 00734 function filterByCategories( &$rows, FormOptions $opts ) { 00735 $categories = array_map( 'trim', explode( '|', $opts['categories'] ) ); 00736 00737 if ( !count( $categories ) ) { 00738 return; 00739 } 00740 00741 # Filter categories 00742 $cats = array(); 00743 foreach ( $categories as $cat ) { 00744 $cat = trim( $cat ); 00745 if ( $cat == '' ) { 00746 continue; 00747 } 00748 $cats[] = $cat; 00749 } 00750 00751 # Filter articles 00752 $articles = array(); 00753 $a2r = array(); 00754 $rowsarr = array(); 00755 foreach ( $rows as $k => $r ) { 00756 $nt = Title::makeTitle( $r->rc_namespace, $r->rc_title ); 00757 $id = $nt->getArticleID(); 00758 if ( $id == 0 ) { 00759 continue; # Page might have been deleted... 00760 } 00761 if ( !in_array( $id, $articles ) ) { 00762 $articles[] = $id; 00763 } 00764 if ( !isset( $a2r[$id] ) ) { 00765 $a2r[$id] = array(); 00766 } 00767 $a2r[$id][] = $k; 00768 $rowsarr[$k] = $r; 00769 } 00770 00771 # Shortcut? 00772 if ( !count( $articles ) || !count( $cats ) ) { 00773 return; 00774 } 00775 00776 # Look up 00777 $c = new Categoryfinder; 00778 $c->seed( $articles, $cats, $opts['categories_any'] ? 'OR' : 'AND' ); 00779 $match = $c->run(); 00780 00781 # Filter 00782 $newrows = array(); 00783 foreach ( $match as $id ) { 00784 foreach ( $a2r[$id] as $rev ) { 00785 $k = $rev; 00786 $newrows[$k] = $rowsarr[$k]; 00787 } 00788 } 00789 $rows = $newrows; 00790 } 00791 00801 function makeOptionsLink( $title, $override, $options, $active = false ) { 00802 $params = $override + $options; 00803 00804 // Bug 36524: false values have be converted to "0" otherwise 00805 // wfArrayToCgi() will omit it them. 00806 foreach ( $params as &$value ) { 00807 if ( $value === false ) { 00808 $value = '0'; 00809 } 00810 } 00811 unset( $value ); 00812 00813 $text = htmlspecialchars( $title ); 00814 if ( $active ) { 00815 $text = '<strong>' . $text . '</strong>'; 00816 } 00817 00818 return Linker::linkKnown( $this->getTitle(), $text, array(), $params ); 00819 } 00820 00828 function optionsPanel( $defaults, $nondefaults ) { 00829 global $wgRCLinkLimits, $wgRCLinkDays; 00830 00831 $options = $nondefaults + $defaults; 00832 00833 $note = ''; 00834 $msg = $this->msg( 'rclegend' ); 00835 if ( !$msg->isDisabled() ) { 00836 $note .= '<div class="mw-rclegend">' . $msg->parse() . "</div>\n"; 00837 } 00838 00839 $lang = $this->getLanguage(); 00840 $user = $this->getUser(); 00841 if ( $options['from'] ) { 00842 $note .= $this->msg( 'rcnotefrom' )->numParams( $options['limit'] )->params( 00843 $lang->userTimeAndDate( $options['from'], $user ), 00844 $lang->userDate( $options['from'], $user ), 00845 $lang->userTime( $options['from'], $user ) )->parse() . '<br />'; 00846 } 00847 00848 # Sort data for display and make sure it's unique after we've added user data. 00849 $linkLimits = $wgRCLinkLimits; 00850 $linkLimits[] = $options['limit']; 00851 sort( $linkLimits ); 00852 $linkLimits = array_unique( $linkLimits ); 00853 00854 $linkDays = $wgRCLinkDays; 00855 $linkDays[] = $options['days']; 00856 sort( $linkDays ); 00857 $linkDays = array_unique( $linkDays ); 00858 00859 // limit links 00860 $cl = array(); 00861 foreach ( $linkLimits as $value ) { 00862 $cl[] = $this->makeOptionsLink( $lang->formatNum( $value ), 00863 array( 'limit' => $value ), $nondefaults, $value == $options['limit'] ); 00864 } 00865 $cl = $lang->pipeList( $cl ); 00866 00867 // day links, reset 'from' to none 00868 $dl = array(); 00869 foreach ( $linkDays as $value ) { 00870 $dl[] = $this->makeOptionsLink( $lang->formatNum( $value ), 00871 array( 'days' => $value, 'from' => '' ), $nondefaults, $value == $options['days'] ); 00872 } 00873 $dl = $lang->pipeList( $dl ); 00874 00875 // show/hide links 00876 $showhide = array( $this->msg( 'show' )->text(), $this->msg( 'hide' )->text() ); 00877 $filters = array( 00878 'hideminor' => 'rcshowhideminor', 00879 'hidebots' => 'rcshowhidebots', 00880 'hideanons' => 'rcshowhideanons', 00881 'hideliu' => 'rcshowhideliu', 00882 'hidepatrolled' => 'rcshowhidepatr', 00883 'hidemyself' => 'rcshowhidemine' 00884 ); 00885 foreach ( $this->getCustomFilters() as $key => $params ) { 00886 $filters[$key] = $params['msg']; 00887 } 00888 // Disable some if needed 00889 if ( !$user->useRCPatrol() ) { 00890 unset( $filters['hidepatrolled'] ); 00891 } 00892 00893 $links = array(); 00894 foreach ( $filters as $key => $msg ) { 00895 $link = $this->makeOptionsLink( $showhide[1 - $options[$key]], 00896 array( $key => 1 - $options[$key] ), $nondefaults ); 00897 $links[] = $this->msg( $msg )->rawParams( $link )->escaped(); 00898 } 00899 00900 // show from this onward link 00901 $timestamp = wfTimestampNow(); 00902 $now = $lang->userTimeAndDate( $timestamp, $user ); 00903 $tl = $this->makeOptionsLink( 00904 $now, array( 'from' => $timestamp ), $nondefaults 00905 ); 00906 00907 $rclinks = $this->msg( 'rclinks' )->rawParams( $cl, $dl, $lang->pipeList( $links ) ) 00908 ->parse(); 00909 $rclistfrom = $this->msg( 'rclistfrom' )->rawParams( $tl )->parse(); 00910 00911 return "{$note}$rclinks<br />$rclistfrom"; 00912 } 00913 00917 protected function addModules() { 00918 $this->getOutput()->addModules( array( 00919 'mediawiki.special.recentchanges', 00920 ) ); 00921 } 00922 00923 protected function getGroupName() { 00924 return 'changes'; 00925 } 00926 }