MediaWiki
master
|
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 $opts->fetchValuesFromRequest( $this->getRequest() ); 00113 $opts->validateIntBounds( 'limit', 0, $wgFeedLimit ); 00114 return $opts; 00115 } 00116 00120 public function getOptions() { 00121 if ( $this->rcOptions === null ) { 00122 if ( $this->including() ) { 00123 $isFeed = false; 00124 } else { 00125 $isFeed = (bool)$this->getRequest()->getVal( 'feed' ); 00126 } 00127 $this->rcOptions = $isFeed ? $this->feedSetup() : $this->setup( $this->rcSubpage ); 00128 } 00129 return $this->rcOptions; 00130 } 00131 00132 00138 public function execute( $subpage ) { 00139 $this->rcSubpage = $subpage; 00140 $feedFormat = $this->including() ? null : $this->getRequest()->getVal( 'feed' ); 00141 00142 # 10 seconds server-side caching max 00143 $this->getOutput()->setSquidMaxage( 10 ); 00144 # Check if the client has a cached version 00145 $lastmod = $this->checkLastModified( $feedFormat ); 00146 if( $lastmod === false ) { 00147 return; 00148 } 00149 00150 $opts = $this->getOptions(); 00151 $this->setHeaders(); 00152 $this->outputHeader(); 00153 $this->addRecentChangesJS(); 00154 00155 // Fetch results, prepare a batch link existence check query 00156 $conds = $this->buildMainQueryConds( $opts ); 00157 $rows = $this->doMainQuery( $conds, $opts ); 00158 if( $rows === false ){ 00159 if( !$this->including() ) { 00160 $this->doHeader( $opts ); 00161 } 00162 return; 00163 } 00164 00165 if( !$feedFormat ) { 00166 $batch = new LinkBatch; 00167 foreach( $rows as $row ) { 00168 $batch->add( NS_USER, $row->rc_user_text ); 00169 $batch->add( NS_USER_TALK, $row->rc_user_text ); 00170 $batch->add( $row->rc_namespace, $row->rc_title ); 00171 } 00172 $batch->execute(); 00173 } 00174 if( $feedFormat ) { 00175 list( $changesFeed, $formatter ) = $this->getFeedObject( $feedFormat ); 00176 $changesFeed->execute( $formatter, $rows, $lastmod, $opts ); 00177 } else { 00178 $this->webOutput( $rows, $opts ); 00179 } 00180 00181 $rows->free(); 00182 } 00183 00189 public function getFeedObject( $feedFormat ){ 00190 $changesFeed = new ChangesFeed( $feedFormat, 'rcfeed' ); 00191 $formatter = $changesFeed->getFeedObject( 00192 $this->msg( 'recentchanges' )->inContentLanguage()->text(), 00193 $this->msg( 'recentchanges-feed-description' )->inContentLanguage()->text(), 00194 $this->getTitle()->getFullURL() 00195 ); 00196 return array( $changesFeed, $formatter ); 00197 } 00198 00206 public function parseParameters( $par, FormOptions $opts ) { 00207 $bits = preg_split( '/\s*,\s*/', trim( $par ) ); 00208 foreach( $bits as $bit ) { 00209 if( 'hidebots' === $bit ) { 00210 $opts['hidebots'] = true; 00211 } 00212 if( 'bots' === $bit ) { 00213 $opts['hidebots'] = false; 00214 } 00215 if( 'hideminor' === $bit ) { 00216 $opts['hideminor'] = true; 00217 } 00218 if( 'minor' === $bit ) { 00219 $opts['hideminor'] = false; 00220 } 00221 if( 'hideliu' === $bit ) { 00222 $opts['hideliu'] = true; 00223 } 00224 if( 'hidepatrolled' === $bit ) { 00225 $opts['hidepatrolled'] = true; 00226 } 00227 if( 'hideanons' === $bit ) { 00228 $opts['hideanons'] = true; 00229 } 00230 if( 'hidemyself' === $bit ) { 00231 $opts['hidemyself'] = true; 00232 } 00233 00234 if( is_numeric( $bit ) ) { 00235 $opts['limit'] = $bit; 00236 } 00237 00238 $m = array(); 00239 if( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) { 00240 $opts['limit'] = $m[1]; 00241 } 00242 if( preg_match( '/^days=(\d+)$/', $bit, $m ) ) { 00243 $opts['days'] = $m[1]; 00244 } 00245 if( preg_match( '/^namespace=(\d+)$/', $bit, $m ) ) { 00246 $opts['namespace'] = $m[1]; 00247 } 00248 } 00249 } 00250 00259 public function checkLastModified( $feedFormat ) { 00260 $dbr = wfGetDB( DB_SLAVE ); 00261 $lastmod = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', false, __METHOD__ ); 00262 if( $feedFormat || !$this->getUser()->useRCPatrol() ) { 00263 if( $lastmod && $this->getOutput()->checkLastModified( $lastmod ) ) { 00264 # Client cache fresh and headers sent, nothing more to do. 00265 return false; 00266 } 00267 } 00268 return $lastmod; 00269 } 00270 00277 public function buildMainQueryConds( FormOptions $opts ) { 00278 $dbr = wfGetDB( DB_SLAVE ); 00279 $conds = array(); 00280 00281 # It makes no sense to hide both anons and logged-in users 00282 # Where this occurs, force anons to be shown 00283 $forcebot = false; 00284 if( $opts['hideanons'] && $opts['hideliu'] ){ 00285 # Check if the user wants to show bots only 00286 if( $opts['hidebots'] ){ 00287 $opts['hideanons'] = false; 00288 } else { 00289 $forcebot = true; 00290 $opts['hidebots'] = false; 00291 } 00292 } 00293 00294 // Calculate cutoff 00295 $cutoff_unixtime = time() - ( $opts['days'] * 86400 ); 00296 $cutoff_unixtime = $cutoff_unixtime - ($cutoff_unixtime % 86400); 00297 $cutoff = $dbr->timestamp( $cutoff_unixtime ); 00298 00299 $fromValid = preg_match('/^[0-9]{14}$/', $opts['from']); 00300 if( $fromValid && $opts['from'] > wfTimestamp(TS_MW,$cutoff) ) { 00301 $cutoff = $dbr->timestamp($opts['from']); 00302 } else { 00303 $opts->reset( 'from' ); 00304 } 00305 00306 $conds[] = 'rc_timestamp >= ' . $dbr->addQuotes( $cutoff ); 00307 00308 $hidePatrol = $this->getUser()->useRCPatrol() && $opts['hidepatrolled']; 00309 $hideLoggedInUsers = $opts['hideliu'] && !$forcebot; 00310 $hideAnonymousUsers = $opts['hideanons'] && !$forcebot; 00311 00312 if( $opts['hideminor'] ) { 00313 $conds['rc_minor'] = 0; 00314 } 00315 if( $opts['hidebots'] ) { 00316 $conds['rc_bot'] = 0; 00317 } 00318 if( $hidePatrol ) { 00319 $conds['rc_patrolled'] = 0; 00320 } 00321 if( $forcebot ) { 00322 $conds['rc_bot'] = 1; 00323 } 00324 if( $hideLoggedInUsers ) { 00325 $conds[] = 'rc_user = 0'; 00326 } 00327 if( $hideAnonymousUsers ) { 00328 $conds[] = 'rc_user != 0'; 00329 } 00330 00331 if( $opts['hidemyself'] ) { 00332 if( $this->getUser()->getId() ) { 00333 $conds[] = 'rc_user != ' . $dbr->addQuotes( $this->getUser()->getId() ); 00334 } else { 00335 $conds[] = 'rc_user_text != ' . $dbr->addQuotes( $this->getUser()->getName() ); 00336 } 00337 } 00338 00339 # Namespace filtering 00340 if( $opts['namespace'] !== '' ) { 00341 $selectedNS = $dbr->addQuotes( $opts['namespace'] ); 00342 $operator = $opts['invert'] ? '!=' : '='; 00343 $boolean = $opts['invert'] ? 'AND' : 'OR'; 00344 00345 # namespace association (bug 2429) 00346 if( !$opts['associated'] ) { 00347 $condition = "rc_namespace $operator $selectedNS"; 00348 } else { 00349 # Also add the associated namespace 00350 $associatedNS = $dbr->addQuotes( 00351 MWNamespace::getAssociated( $opts['namespace'] ) 00352 ); 00353 $condition = "(rc_namespace $operator $selectedNS " 00354 . $boolean 00355 . " rc_namespace $operator $associatedNS)"; 00356 } 00357 00358 $conds[] = $condition; 00359 } 00360 return $conds; 00361 } 00362 00370 public function doMainQuery( $conds, $opts ) { 00371 $tables = array( 'recentchanges' ); 00372 $join_conds = array(); 00373 $query_options = array( 00374 'USE INDEX' => array( 'recentchanges' => 'rc_timestamp' ) 00375 ); 00376 00377 $uid = $this->getUser()->getId(); 00378 $dbr = wfGetDB( DB_SLAVE ); 00379 $limit = $opts['limit']; 00380 $namespace = $opts['namespace']; 00381 $invert = $opts['invert']; 00382 $associated = $opts['associated']; 00383 00384 $fields = RecentChange::selectFields(); 00385 // JOIN on watchlist for users 00386 if ( $uid ) { 00387 $tables[] = 'watchlist'; 00388 $fields[] = 'wl_user'; 00389 $fields[] = 'wl_notificationtimestamp'; 00390 $join_conds['watchlist'] = array('LEFT JOIN', 00391 "wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace"); 00392 } 00393 if ( $this->getUser()->isAllowed( 'rollback' ) ) { 00394 $tables[] = 'page'; 00395 $fields[] = 'page_latest'; 00396 $join_conds['page'] = array('LEFT JOIN', 'rc_cur_id=page_id'); 00397 } 00398 // Tag stuff. 00399 ChangeTags::modifyDisplayQuery( 00400 $tables, 00401 $fields, 00402 $conds, 00403 $join_conds, 00404 $query_options, 00405 $opts['tagfilter'] 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 $feedQuery = $this->getFeedQuery(); 00484 if ( $feedQuery !== '' ) { 00485 $this->getOutput()->setFeedAppendQuery( $feedQuery ); 00486 } else { 00487 $this->getOutput()->setFeedAppendQuery( false ); 00488 } 00489 00490 if( $wgAllowCategorizedRecentChanges ) { 00491 $this->filterByCategories( $rows, $opts ); 00492 } 00493 00494 $showWatcherCount = $wgRCShowWatchingUsers && $this->getUser()->getOption( 'shownumberswatching' ); 00495 $watcherCache = array(); 00496 00497 $dbr = wfGetDB( DB_SLAVE ); 00498 00499 $counter = 1; 00500 $list = ChangesList::newFromContext( $this->getContext() ); 00501 00502 $s = $list->beginRecentChangesList(); 00503 foreach( $rows as $obj ) { 00504 if( $limit == 0 ) { 00505 break; 00506 } 00507 $rc = RecentChange::newFromRow( $obj ); 00508 $rc->counter = $counter++; 00509 # Check if the page has been updated since the last visit 00510 if( $wgShowUpdatedMarker && !empty( $obj->wl_notificationtimestamp ) ) { 00511 $rc->notificationtimestamp = ( $obj->rc_timestamp >= $obj->wl_notificationtimestamp ); 00512 } else { 00513 $rc->notificationtimestamp = false; // Default 00514 } 00515 # Check the number of users watching the page 00516 $rc->numberofWatchingusers = 0; // Default 00517 if( $showWatcherCount && $obj->rc_namespace >= 0 ) { 00518 if( !isset( $watcherCache[$obj->rc_namespace][$obj->rc_title] ) ) { 00519 $watcherCache[$obj->rc_namespace][$obj->rc_title] = 00520 $dbr->selectField( 00521 'watchlist', 00522 'COUNT(*)', 00523 array( 00524 'wl_namespace' => $obj->rc_namespace, 00525 'wl_title' => $obj->rc_title, 00526 ), 00527 __METHOD__ . '-watchers' 00528 ); 00529 } 00530 $rc->numberofWatchingusers = $watcherCache[$obj->rc_namespace][$obj->rc_title]; 00531 } 00532 $s .= $list->recentChangesLine( $rc, !empty( $obj->wl_user ), $counter ); 00533 --$limit; 00534 } 00535 $s .= $list->endRecentChangesList(); 00536 $this->getOutput()->addHTML( $s ); 00537 } 00538 00544 public function getFeedQuery() { 00545 global $wgFeedLimit; 00546 00547 $this->getOptions()->validateIntBounds( 'limit', 0, $wgFeedLimit ); 00548 $options = $this->getOptions()->getChangedValues(); 00549 00550 // wfArrayToCgi() omits options set to null or false 00551 foreach ( $options as &$value ) { 00552 if ( $value === false ) { 00553 $value = '0'; 00554 } 00555 } 00556 unset( $value ); 00557 00558 return wfArrayToCgi( $options ); 00559 } 00560 00567 public function doHeader( $opts ) { 00568 global $wgScript; 00569 00570 $this->setTopText( $opts ); 00571 00572 $defaults = $opts->getAllValues(); 00573 $nondefaults = $opts->getChangedValues(); 00574 $opts->consumeValues( array( 00575 'namespace', 'invert', 'associated', 'tagfilter', 00576 'categories', 'categories_any' 00577 ) ); 00578 00579 $panel = array(); 00580 $panel[] = $this->optionsPanel( $defaults, $nondefaults ); 00581 $panel[] = '<hr />'; 00582 00583 $extraOpts = $this->getExtraOptions( $opts ); 00584 $extraOptsCount = count( $extraOpts ); 00585 $count = 0; 00586 $submit = ' ' . Xml::submitbutton( $this->msg( 'allpagessubmit' )->text() ); 00587 00588 $out = Xml::openElement( 'table', array( 'class' => 'mw-recentchanges-table' ) ); 00589 foreach( $extraOpts as $name => $optionRow ) { 00590 # Add submit button to the last row only 00591 ++$count; 00592 $addSubmit = ( $count === $extraOptsCount ) ? $submit : ''; 00593 00594 $out .= Xml::openElement( 'tr' ); 00595 if( is_array( $optionRow ) ) { 00596 $out .= Xml::tags( 'td', array( 'class' => 'mw-label mw-' . $name . '-label' ), $optionRow[0] ); 00597 $out .= Xml::tags( 'td', array( 'class' => 'mw-input' ), $optionRow[1] . $addSubmit ); 00598 } else { 00599 $out .= Xml::tags( 'td', array( 'class' => 'mw-input', 'colspan' => 2 ), $optionRow . $addSubmit ); 00600 } 00601 $out .= Xml::closeElement( 'tr' ); 00602 } 00603 $out .= Xml::closeElement( 'table' ); 00604 00605 $unconsumed = $opts->getUnconsumedValues(); 00606 foreach( $unconsumed as $key => $value ) { 00607 $out .= Html::hidden( $key, $value ); 00608 } 00609 00610 $t = $this->getTitle(); 00611 $out .= Html::hidden( 'title', $t->getPrefixedText() ); 00612 $form = Xml::tags( 'form', array( 'action' => $wgScript ), $out ); 00613 $panel[] = $form; 00614 $panelString = implode( "\n", $panel ); 00615 00616 $this->getOutput()->addHTML( 00617 Xml::fieldset( $this->msg( 'recentchanges-legend' )->text(), $panelString, array( 'class' => 'rcoptions' ) ) 00618 ); 00619 00620 $this->setBottomText( $opts ); 00621 } 00622 00629 function getExtraOptions( $opts ) { 00630 $extraOpts = array(); 00631 $extraOpts['namespace'] = $this->namespaceFilterForm( $opts ); 00632 00633 global $wgAllowCategorizedRecentChanges; 00634 if( $wgAllowCategorizedRecentChanges ) { 00635 $extraOpts['category'] = $this->categoryFilterForm( $opts ); 00636 } 00637 00638 $tagFilter = ChangeTags::buildTagFilterSelector( $opts['tagfilter'] ); 00639 if ( count( $tagFilter ) ) { 00640 $extraOpts['tagfilter'] = $tagFilter; 00641 } 00642 00643 wfRunHooks( 'SpecialRecentChangesPanel', array( &$extraOpts, $opts ) ); 00644 return $extraOpts; 00645 } 00646 00652 function setTopText( FormOptions $opts ) { 00653 global $wgContLang; 00654 00655 $message = $this->msg( 'recentchangestext' )->inContentLanguage(); 00656 if ( !$message->isDisabled() ) { 00657 $this->getOutput()->addWikiText( 00658 Html::rawElement( 'p', 00659 array( 'lang' => $wgContLang->getCode(), 'dir' => $wgContLang->getDir() ), 00660 "\n" . $message->plain() . "\n" 00661 ), 00662 /* $lineStart */ false, 00663 /* $interface */ false 00664 ); 00665 } 00666 } 00667 00674 function setBottomText( FormOptions $opts ) {} 00675 00683 protected function namespaceFilterForm( FormOptions $opts ) { 00684 $nsSelect = Html::namespaceSelector( 00685 array( 'selected' => $opts['namespace'], 'all' => '' ), 00686 array( 'name' => 'namespace', 'id' => 'namespace' ) 00687 ); 00688 $nsLabel = Xml::label( $this->msg( 'namespace' )->text(), 'namespace' ); 00689 $invert = Xml::checkLabel( 00690 $this->msg( 'invert' )->text(), 'invert', 'nsinvert', 00691 $opts['invert'], 00692 array( 'title' => $this->msg( 'tooltip-invert' )->text() ) 00693 ); 00694 $associated = Xml::checkLabel( 00695 $this->msg( 'namespace_association' )->text(), 'associated', 'nsassociated', 00696 $opts['associated'], 00697 array( 'title' => $this->msg( 'tooltip-namespace_association' )->text() ) 00698 ); 00699 return array( $nsLabel, "$nsSelect $invert $associated" ); 00700 } 00701 00708 protected function categoryFilterForm( FormOptions $opts ) { 00709 list( $label, $input ) = Xml::inputLabelSep( $this->msg( 'rc_categories' )->text(), 00710 'categories', 'mw-categories', false, $opts['categories'] ); 00711 00712 $input .= ' ' . Xml::checkLabel( $this->msg( 'rc_categories_any' )->text(), 00713 'categories_any', 'mw-categories_any', $opts['categories_any'] ); 00714 00715 return array( $label, $input ); 00716 } 00717 00724 function filterByCategories( &$rows, FormOptions $opts ) { 00725 $categories = array_map( 'trim', explode( '|' , $opts['categories'] ) ); 00726 00727 if( !count( $categories ) ) { 00728 return; 00729 } 00730 00731 # Filter categories 00732 $cats = array(); 00733 foreach( $categories as $cat ) { 00734 $cat = trim( $cat ); 00735 if( $cat == '' ) { 00736 continue; 00737 } 00738 $cats[] = $cat; 00739 } 00740 00741 # Filter articles 00742 $articles = array(); 00743 $a2r = array(); 00744 $rowsarr = array(); 00745 foreach( $rows as $k => $r ) { 00746 $nt = Title::makeTitle( $r->rc_namespace, $r->rc_title ); 00747 $id = $nt->getArticleID(); 00748 if( $id == 0 ) { 00749 continue; # Page might have been deleted... 00750 } 00751 if( !in_array( $id, $articles ) ) { 00752 $articles[] = $id; 00753 } 00754 if( !isset( $a2r[$id] ) ) { 00755 $a2r[$id] = array(); 00756 } 00757 $a2r[$id][] = $k; 00758 $rowsarr[$k] = $r; 00759 } 00760 00761 # Shortcut? 00762 if( !count( $articles ) || !count( $cats ) ) { 00763 return; 00764 } 00765 00766 # Look up 00767 $c = new Categoryfinder; 00768 $c->seed( $articles, $cats, $opts['categories_any'] ? 'OR' : 'AND' ); 00769 $match = $c->run(); 00770 00771 # Filter 00772 $newrows = array(); 00773 foreach( $match as $id ) { 00774 foreach( $a2r[$id] as $rev ) { 00775 $k = $rev; 00776 $newrows[$k] = $rowsarr[$k]; 00777 } 00778 } 00779 $rows = $newrows; 00780 } 00781 00791 function makeOptionsLink( $title, $override, $options, $active = false ) { 00792 $params = $override + $options; 00793 00794 // Bug 36524: false values have be converted to "0" otherwise 00795 // wfArrayToCgi() will omit it them. 00796 foreach ( $params as &$value ) { 00797 if ( $value === false ) { 00798 $value = '0'; 00799 } 00800 } 00801 unset( $value ); 00802 00803 $text = htmlspecialchars( $title ); 00804 if ( $active ) { 00805 $text = '<strong>' . $text . '</strong>'; 00806 } 00807 return Linker::linkKnown( $this->getTitle(), $text, array(), $params ); 00808 } 00809 00817 function optionsPanel( $defaults, $nondefaults ) { 00818 global $wgRCLinkLimits, $wgRCLinkDays; 00819 00820 $options = $nondefaults + $defaults; 00821 00822 $note = ''; 00823 $msg = $this->msg( 'rclegend' ); 00824 if( !$msg->isDisabled() ) { 00825 $note .= '<div class="mw-rclegend">' . $msg->parse() . "</div>\n"; 00826 } 00827 00828 $lang = $this->getLanguage(); 00829 $user = $this->getUser(); 00830 if( $options['from'] ) { 00831 $note .= $this->msg( 'rcnotefrom' )->numParams( $options['limit'] )->params( 00832 $lang->userTimeAndDate( $options['from'], $user ), 00833 $lang->userDate( $options['from'], $user ), 00834 $lang->userTime( $options['from'], $user ) )->parse() . '<br />'; 00835 } 00836 00837 # Sort data for display and make sure it's unique after we've added user data. 00838 $wgRCLinkLimits[] = $options['limit']; 00839 $wgRCLinkDays[] = $options['days']; 00840 sort( $wgRCLinkLimits ); 00841 sort( $wgRCLinkDays ); 00842 $wgRCLinkLimits = array_unique( $wgRCLinkLimits ); 00843 $wgRCLinkDays = array_unique( $wgRCLinkDays ); 00844 00845 // limit links 00846 foreach( $wgRCLinkLimits as $value ) { 00847 $cl[] = $this->makeOptionsLink( $lang->formatNum( $value ), 00848 array( 'limit' => $value ), $nondefaults, $value == $options['limit'] ); 00849 } 00850 $cl = $lang->pipeList( $cl ); 00851 00852 // day links, reset 'from' to none 00853 foreach( $wgRCLinkDays as $value ) { 00854 $dl[] = $this->makeOptionsLink( $lang->formatNum( $value ), 00855 array( 'days' => $value, 'from' => '' ), $nondefaults, $value == $options['days'] ); 00856 } 00857 $dl = $lang->pipeList( $dl ); 00858 00859 00860 // show/hide links 00861 $showhide = array( $this->msg( 'show' )->text(), $this->msg( 'hide' )->text() ); 00862 $filters = array( 00863 'hideminor' => 'rcshowhideminor', 00864 'hidebots' => 'rcshowhidebots', 00865 'hideanons' => 'rcshowhideanons', 00866 'hideliu' => 'rcshowhideliu', 00867 'hidepatrolled' => 'rcshowhidepatr', 00868 'hidemyself' => 'rcshowhidemine' 00869 ); 00870 foreach ( $this->getCustomFilters() as $key => $params ) { 00871 $filters[$key] = $params['msg']; 00872 } 00873 // Disable some if needed 00874 if ( !$user->useRCPatrol() ) { 00875 unset( $filters['hidepatrolled'] ); 00876 } 00877 00878 $links = array(); 00879 foreach ( $filters as $key => $msg ) { 00880 $link = $this->makeOptionsLink( $showhide[1 - $options[$key]], 00881 array( $key => 1-$options[$key] ), $nondefaults ); 00882 $links[] = $this->msg( $msg )->rawParams( $link )->escaped(); 00883 } 00884 00885 // show from this onward link 00886 $timestamp = wfTimestampNow(); 00887 $now = $lang->userTimeAndDate( $timestamp, $user ); 00888 $tl = $this->makeOptionsLink( 00889 $now, array( 'from' => $timestamp ), $nondefaults 00890 ); 00891 00892 $rclinks = $this->msg( 'rclinks' )->rawParams( $cl, $dl, $lang->pipeList( $links ) )->parse(); 00893 $rclistfrom = $this->msg( 'rclistfrom' )->rawParams( $tl )->parse(); 00894 return "{$note}$rclinks<br />$rclistfrom"; 00895 } 00896 00900 function addRecentChangesJS() { 00901 $this->getOutput()->addModules( array( 00902 'mediawiki.special.recentchanges', 00903 ) ); 00904 } 00905 }