MediaWiki
REL1_20
|
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 $this->msg( 'recentchanges' )->inContentLanguage()->text(), 00194 $this->msg( 'recentchanges-feed-description' )->inContentLanguage()->text(), 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 // Tag stuff. 00400 ChangeTags::modifyDisplayQuery( 00401 $tables, 00402 $fields, 00403 $conds, 00404 $join_conds, 00405 $query_options, 00406 $opts['tagfilter'] 00407 ); 00408 00409 if ( !wfRunHooks( 'SpecialRecentChangesQuery', 00410 array( &$conds, &$tables, &$join_conds, $opts, &$query_options, &$fields ) ) ) 00411 { 00412 return false; 00413 } 00414 00415 // Don't use the new_namespace_time timestamp index if: 00416 // (a) "All namespaces" selected 00417 // (b) We want pages in more than one namespace (inverted/associated) 00418 // (c) There is a tag to filter on (use tag index instead) 00419 // (d) UNION + sort/limit is not an option for the DBMS 00420 if( $namespace === '' 00421 || ( $invert || $associated ) 00422 || $opts['tagfilter'] != '' 00423 || !$dbr->unionSupportsOrderAndLimit() ) 00424 { 00425 $res = $dbr->select( $tables, $fields, $conds, __METHOD__, 00426 array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit ) + 00427 $query_options, 00428 $join_conds ); 00429 // We have a new_namespace_time index! UNION over new=(0,1) and sort result set! 00430 } else { 00431 // New pages 00432 $sqlNew = $dbr->selectSQLText( 00433 $tables, 00434 $fields, 00435 array( 'rc_new' => 1 ) + $conds, 00436 __METHOD__, 00437 array( 00438 'ORDER BY' => 'rc_timestamp DESC', 00439 'LIMIT' => $limit, 00440 'USE INDEX' => array( 'recentchanges' => 'new_name_timestamp' ) 00441 ), 00442 $join_conds 00443 ); 00444 // Old pages 00445 $sqlOld = $dbr->selectSQLText( 00446 $tables, 00447 $fields, 00448 array( 'rc_new' => 0 ) + $conds, 00449 __METHOD__, 00450 array( 00451 'ORDER BY' => 'rc_timestamp DESC', 00452 'LIMIT' => $limit, 00453 'USE INDEX' => array( 'recentchanges' => 'new_name_timestamp' ) 00454 ), 00455 $join_conds 00456 ); 00457 # Join the two fast queries, and sort the result set 00458 $sql = $dbr->unionQueries( array( $sqlNew, $sqlOld ), false ) . 00459 ' ORDER BY rc_timestamp DESC'; 00460 $sql = $dbr->limitResult( $sql, $limit, false ); 00461 $res = $dbr->query( $sql, __METHOD__ ); 00462 } 00463 00464 return $res; 00465 } 00466 00473 public function webOutput( $rows, $opts ) { 00474 global $wgRCShowWatchingUsers, $wgShowUpdatedMarker, $wgAllowCategorizedRecentChanges; 00475 00476 $limit = $opts['limit']; 00477 00478 if( !$this->including() ) { 00479 // Output options box 00480 $this->doHeader( $opts ); 00481 } 00482 00483 // And now for the content 00484 $this->getOutput()->setFeedAppendQuery( $this->getFeedQuery() ); 00485 00486 if( $wgAllowCategorizedRecentChanges ) { 00487 $this->filterByCategories( $rows, $opts ); 00488 } 00489 00490 $showWatcherCount = $wgRCShowWatchingUsers && $this->getUser()->getOption( 'shownumberswatching' ); 00491 $watcherCache = array(); 00492 00493 $dbr = wfGetDB( DB_SLAVE ); 00494 00495 $counter = 1; 00496 $list = ChangesList::newFromContext( $this->getContext() ); 00497 00498 $s = $list->beginRecentChangesList(); 00499 foreach( $rows as $obj ) { 00500 if( $limit == 0 ) { 00501 break; 00502 } 00503 $rc = RecentChange::newFromRow( $obj ); 00504 $rc->counter = $counter++; 00505 # Check if the page has been updated since the last visit 00506 if( $wgShowUpdatedMarker && !empty( $obj->wl_notificationtimestamp ) ) { 00507 $rc->notificationtimestamp = ( $obj->rc_timestamp >= $obj->wl_notificationtimestamp ); 00508 } else { 00509 $rc->notificationtimestamp = false; // Default 00510 } 00511 # Check the number of users watching the page 00512 $rc->numberofWatchingusers = 0; // Default 00513 if( $showWatcherCount && $obj->rc_namespace >= 0 ) { 00514 if( !isset( $watcherCache[$obj->rc_namespace][$obj->rc_title] ) ) { 00515 $watcherCache[$obj->rc_namespace][$obj->rc_title] = 00516 $dbr->selectField( 00517 'watchlist', 00518 'COUNT(*)', 00519 array( 00520 'wl_namespace' => $obj->rc_namespace, 00521 'wl_title' => $obj->rc_title, 00522 ), 00523 __METHOD__ . '-watchers' 00524 ); 00525 } 00526 $rc->numberofWatchingusers = $watcherCache[$obj->rc_namespace][$obj->rc_title]; 00527 } 00528 $s .= $list->recentChangesLine( $rc, !empty( $obj->wl_user ), $counter ); 00529 --$limit; 00530 } 00531 $s .= $list->endRecentChangesList(); 00532 $this->getOutput()->addHTML( $s ); 00533 } 00534 00540 public function getFeedQuery() { 00541 return false; 00542 } 00543 00550 public function doHeader( $opts ) { 00551 global $wgScript; 00552 00553 $this->setTopText( $opts ); 00554 00555 $defaults = $opts->getAllValues(); 00556 $nondefaults = $opts->getChangedValues(); 00557 $opts->consumeValues( array( 00558 'namespace', 'invert', 'associated', 'tagfilter', 00559 'categories', 'categories_any' 00560 ) ); 00561 00562 $panel = array(); 00563 $panel[] = $this->optionsPanel( $defaults, $nondefaults ); 00564 $panel[] = '<hr />'; 00565 00566 $extraOpts = $this->getExtraOptions( $opts ); 00567 $extraOptsCount = count( $extraOpts ); 00568 $count = 0; 00569 $submit = ' ' . Xml::submitbutton( $this->msg( 'allpagessubmit' )->text() ); 00570 00571 $out = Xml::openElement( 'table', array( 'class' => 'mw-recentchanges-table' ) ); 00572 foreach( $extraOpts as $name => $optionRow ) { 00573 # Add submit button to the last row only 00574 ++$count; 00575 $addSubmit = ( $count === $extraOptsCount ) ? $submit : ''; 00576 00577 $out .= Xml::openElement( 'tr' ); 00578 if( is_array( $optionRow ) ) { 00579 $out .= Xml::tags( 'td', array( 'class' => 'mw-label mw-' . $name . '-label' ), $optionRow[0] ); 00580 $out .= Xml::tags( 'td', array( 'class' => 'mw-input' ), $optionRow[1] . $addSubmit ); 00581 } else { 00582 $out .= Xml::tags( 'td', array( 'class' => 'mw-input', 'colspan' => 2 ), $optionRow . $addSubmit ); 00583 } 00584 $out .= Xml::closeElement( 'tr' ); 00585 } 00586 $out .= Xml::closeElement( 'table' ); 00587 00588 $unconsumed = $opts->getUnconsumedValues(); 00589 foreach( $unconsumed as $key => $value ) { 00590 $out .= Html::hidden( $key, $value ); 00591 } 00592 00593 $t = $this->getTitle(); 00594 $out .= Html::hidden( 'title', $t->getPrefixedText() ); 00595 $form = Xml::tags( 'form', array( 'action' => $wgScript ), $out ); 00596 $panel[] = $form; 00597 $panelString = implode( "\n", $panel ); 00598 00599 $this->getOutput()->addHTML( 00600 Xml::fieldset( $this->msg( 'recentchanges-legend' )->text(), $panelString, array( 'class' => 'rcoptions' ) ) 00601 ); 00602 00603 $this->setBottomText( $opts ); 00604 } 00605 00612 function getExtraOptions( $opts ) { 00613 $extraOpts = array(); 00614 $extraOpts['namespace'] = $this->namespaceFilterForm( $opts ); 00615 00616 global $wgAllowCategorizedRecentChanges; 00617 if( $wgAllowCategorizedRecentChanges ) { 00618 $extraOpts['category'] = $this->categoryFilterForm( $opts ); 00619 } 00620 00621 $tagFilter = ChangeTags::buildTagFilterSelector( $opts['tagfilter'] ); 00622 if ( count( $tagFilter ) ) { 00623 $extraOpts['tagfilter'] = $tagFilter; 00624 } 00625 00626 wfRunHooks( 'SpecialRecentChangesPanel', array( &$extraOpts, $opts ) ); 00627 return $extraOpts; 00628 } 00629 00635 function setTopText( FormOptions $opts ) { 00636 global $wgContLang; 00637 00638 $message = $this->msg( 'recentchangestext' )->inContentLanguage(); 00639 if ( !$message->isDisabled() ) { 00640 $this->getOutput()->addWikiText( 00641 Html::rawElement( 'p', 00642 array( 'lang' => $wgContLang->getCode(), 'dir' => $wgContLang->getDir() ), 00643 "\n" . $message->plain() . "\n" 00644 ), 00645 /* $lineStart */ false, 00646 /* $interface */ false 00647 ); 00648 } 00649 } 00650 00657 function setBottomText( FormOptions $opts ) {} 00658 00666 protected function namespaceFilterForm( FormOptions $opts ) { 00667 $nsSelect = Html::namespaceSelector( 00668 array( 'selected' => $opts['namespace'], 'all' => '' ), 00669 array( 'name' => 'namespace', 'id' => 'namespace' ) 00670 ); 00671 $nsLabel = Xml::label( $this->msg( 'namespace' )->text(), 'namespace' ); 00672 $invert = Xml::checkLabel( 00673 $this->msg( 'invert' )->text(), 'invert', 'nsinvert', 00674 $opts['invert'], 00675 array( 'title' => $this->msg( 'tooltip-invert' )->text() ) 00676 ); 00677 $associated = Xml::checkLabel( 00678 $this->msg( 'namespace_association' )->text(), 'associated', 'nsassociated', 00679 $opts['associated'], 00680 array( 'title' => $this->msg( 'tooltip-namespace_association' )->text() ) 00681 ); 00682 return array( $nsLabel, "$nsSelect $invert $associated" ); 00683 } 00684 00691 protected function categoryFilterForm( FormOptions $opts ) { 00692 list( $label, $input ) = Xml::inputLabelSep( $this->msg( 'rc_categories' )->text(), 00693 'categories', 'mw-categories', false, $opts['categories'] ); 00694 00695 $input .= ' ' . Xml::checkLabel( $this->msg( 'rc_categories_any' )->text(), 00696 'categories_any', 'mw-categories_any', $opts['categories_any'] ); 00697 00698 return array( $label, $input ); 00699 } 00700 00707 function filterByCategories( &$rows, FormOptions $opts ) { 00708 $categories = array_map( 'trim', explode( '|' , $opts['categories'] ) ); 00709 00710 if( !count( $categories ) ) { 00711 return; 00712 } 00713 00714 # Filter categories 00715 $cats = array(); 00716 foreach( $categories as $cat ) { 00717 $cat = trim( $cat ); 00718 if( $cat == '' ) { 00719 continue; 00720 } 00721 $cats[] = $cat; 00722 } 00723 00724 # Filter articles 00725 $articles = array(); 00726 $a2r = array(); 00727 $rowsarr = array(); 00728 foreach( $rows as $k => $r ) { 00729 $nt = Title::makeTitle( $r->rc_namespace, $r->rc_title ); 00730 $id = $nt->getArticleID(); 00731 if( $id == 0 ) { 00732 continue; # Page might have been deleted... 00733 } 00734 if( !in_array( $id, $articles ) ) { 00735 $articles[] = $id; 00736 } 00737 if( !isset( $a2r[$id] ) ) { 00738 $a2r[$id] = array(); 00739 } 00740 $a2r[$id][] = $k; 00741 $rowsarr[$k] = $r; 00742 } 00743 00744 # Shortcut? 00745 if( !count( $articles ) || !count( $cats ) ) { 00746 return; 00747 } 00748 00749 # Look up 00750 $c = new Categoryfinder; 00751 $c->seed( $articles, $cats, $opts['categories_any'] ? 'OR' : 'AND' ); 00752 $match = $c->run(); 00753 00754 # Filter 00755 $newrows = array(); 00756 foreach( $match as $id ) { 00757 foreach( $a2r[$id] as $rev ) { 00758 $k = $rev; 00759 $newrows[$k] = $rowsarr[$k]; 00760 } 00761 } 00762 $rows = $newrows; 00763 } 00764 00774 function makeOptionsLink( $title, $override, $options, $active = false ) { 00775 $params = $override + $options; 00776 00777 // Bug 36524: false values have be converted to "0" otherwise 00778 // wfArrayToCgi() will omit it them. 00779 foreach ( $params as &$value ) { 00780 if ( $value === false ) { 00781 $value = '0'; 00782 } 00783 } 00784 unset( $value ); 00785 00786 $text = htmlspecialchars( $title ); 00787 if ( $active ) { 00788 $text = '<strong>' . $text . '</strong>'; 00789 } 00790 return Linker::linkKnown( $this->getTitle(), $text, array(), $params ); 00791 } 00792 00800 function optionsPanel( $defaults, $nondefaults ) { 00801 global $wgRCLinkLimits, $wgRCLinkDays; 00802 00803 $options = $nondefaults + $defaults; 00804 00805 $note = ''; 00806 $msg = $this->msg( 'rclegend' ); 00807 if( !$msg->isDisabled() ) { 00808 $note .= '<div class="mw-rclegend">' . $msg->parse() . "</div>\n"; 00809 } 00810 00811 $lang = $this->getLanguage(); 00812 $user = $this->getUser(); 00813 if( $options['from'] ) { 00814 $note .= $this->msg( 'rcnotefrom' )->numParams( $options['limit'] )->params( 00815 $lang->userTimeAndDate( $options['from'], $user ), 00816 $lang->userDate( $options['from'], $user ), 00817 $lang->userTime( $options['from'], $user ) )->parse() . '<br />'; 00818 } 00819 00820 # Sort data for display and make sure it's unique after we've added user data. 00821 $wgRCLinkLimits[] = $options['limit']; 00822 $wgRCLinkDays[] = $options['days']; 00823 sort( $wgRCLinkLimits ); 00824 sort( $wgRCLinkDays ); 00825 $wgRCLinkLimits = array_unique( $wgRCLinkLimits ); 00826 $wgRCLinkDays = array_unique( $wgRCLinkDays ); 00827 00828 // limit links 00829 foreach( $wgRCLinkLimits as $value ) { 00830 $cl[] = $this->makeOptionsLink( $lang->formatNum( $value ), 00831 array( 'limit' => $value ), $nondefaults, $value == $options['limit'] ); 00832 } 00833 $cl = $lang->pipeList( $cl ); 00834 00835 // day links, reset 'from' to none 00836 foreach( $wgRCLinkDays as $value ) { 00837 $dl[] = $this->makeOptionsLink( $lang->formatNum( $value ), 00838 array( 'days' => $value, 'from' => '' ), $nondefaults, $value == $options['days'] ); 00839 } 00840 $dl = $lang->pipeList( $dl ); 00841 00842 00843 // show/hide links 00844 $showhide = array( $this->msg( 'show' )->text(), $this->msg( 'hide' )->text() ); 00845 $filters = array( 00846 'hideminor' => 'rcshowhideminor', 00847 'hidebots' => 'rcshowhidebots', 00848 'hideanons' => 'rcshowhideanons', 00849 'hideliu' => 'rcshowhideliu', 00850 'hidepatrolled' => 'rcshowhidepatr', 00851 'hidemyself' => 'rcshowhidemine' 00852 ); 00853 foreach ( $this->getCustomFilters() as $key => $params ) { 00854 $filters[$key] = $params['msg']; 00855 } 00856 // Disable some if needed 00857 if ( !$user->useRCPatrol() ) { 00858 unset( $filters['hidepatrolled'] ); 00859 } 00860 00861 $links = array(); 00862 foreach ( $filters as $key => $msg ) { 00863 $link = $this->makeOptionsLink( $showhide[1 - $options[$key]], 00864 array( $key => 1-$options[$key] ), $nondefaults ); 00865 $links[] = $this->msg( $msg )->rawParams( $link )->escaped(); 00866 } 00867 00868 // show from this onward link 00869 $timestamp = wfTimestampNow(); 00870 $now = $lang->userTimeAndDate( $timestamp, $user ); 00871 $tl = $this->makeOptionsLink( 00872 $now, array( 'from' => $timestamp ), $nondefaults 00873 ); 00874 00875 $rclinks = $this->msg( 'rclinks' )->rawParams( $cl, $dl, $lang->pipeList( $links ) )->parse(); 00876 $rclistfrom = $this->msg( 'rclistfrom' )->rawParams( $tl )->parse(); 00877 return "{$note}$rclinks<br />$rclistfrom"; 00878 } 00879 00883 function addRecentChangesJS() { 00884 $this->getOutput()->addModules( array( 00885 'mediawiki.special.recentchanges', 00886 ) ); 00887 } 00888 }