MediaWiki
REL1_21
|
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', $this->getUser()->getIntOption( 'rcdays' ) ); 00046 $opts->add( 'limit', $this->getUser()->getIntOption( '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 00137 public function execute( $subpage ) { 00138 $this->rcSubpage = $subpage; 00139 $feedFormat = $this->including() ? null : $this->getRequest()->getVal( 'feed' ); 00140 00141 # 10 seconds server-side caching max 00142 $this->getOutput()->setSquidMaxage( 10 ); 00143 # Check if the client has a cached version 00144 $lastmod = $this->checkLastModified( $feedFormat ); 00145 if( $lastmod === false ) { 00146 return; 00147 } 00148 00149 $opts = $this->getOptions(); 00150 $this->setHeaders(); 00151 $this->outputHeader(); 00152 $this->addRecentChangesJS(); 00153 00154 // Fetch results, prepare a batch link existence check query 00155 $conds = $this->buildMainQueryConds( $opts ); 00156 $rows = $this->doMainQuery( $conds, $opts ); 00157 if( $rows === false ) { 00158 if( !$this->including() ) { 00159 $this->doHeader( $opts ); 00160 } 00161 return; 00162 } 00163 00164 if( !$feedFormat ) { 00165 $batch = new LinkBatch; 00166 foreach( $rows as $row ) { 00167 $batch->add( NS_USER, $row->rc_user_text ); 00168 $batch->add( NS_USER_TALK, $row->rc_user_text ); 00169 $batch->add( $row->rc_namespace, $row->rc_title ); 00170 } 00171 $batch->execute(); 00172 } 00173 if( $feedFormat ) { 00174 list( $changesFeed, $formatter ) = $this->getFeedObject( $feedFormat ); 00175 $changesFeed->execute( $formatter, $rows, $lastmod, $opts ); 00176 } else { 00177 $this->webOutput( $rows, $opts ); 00178 } 00179 00180 $rows->free(); 00181 } 00182 00188 public function getFeedObject( $feedFormat ) { 00189 $changesFeed = new ChangesFeed( $feedFormat, 'rcfeed' ); 00190 $formatter = $changesFeed->getFeedObject( 00191 $this->msg( 'recentchanges' )->inContentLanguage()->text(), 00192 $this->msg( 'recentchanges-feed-description' )->inContentLanguage()->text(), 00193 $this->getTitle()->getFullURL() 00194 ); 00195 return array( $changesFeed, $formatter ); 00196 } 00197 00205 public function parseParameters( $par, FormOptions $opts ) { 00206 $bits = preg_split( '/\s*,\s*/', trim( $par ) ); 00207 foreach( $bits as $bit ) { 00208 if( 'hidebots' === $bit ) { 00209 $opts['hidebots'] = true; 00210 } 00211 if( 'bots' === $bit ) { 00212 $opts['hidebots'] = false; 00213 } 00214 if( 'hideminor' === $bit ) { 00215 $opts['hideminor'] = true; 00216 } 00217 if( 'minor' === $bit ) { 00218 $opts['hideminor'] = false; 00219 } 00220 if( 'hideliu' === $bit ) { 00221 $opts['hideliu'] = true; 00222 } 00223 if( 'hidepatrolled' === $bit ) { 00224 $opts['hidepatrolled'] = true; 00225 } 00226 if( 'hideanons' === $bit ) { 00227 $opts['hideanons'] = true; 00228 } 00229 if( 'hidemyself' === $bit ) { 00230 $opts['hidemyself'] = true; 00231 } 00232 00233 if( is_numeric( $bit ) ) { 00234 $opts['limit'] = $bit; 00235 } 00236 00237 $m = array(); 00238 if( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) { 00239 $opts['limit'] = $m[1]; 00240 } 00241 if( preg_match( '/^days=(\d+)$/', $bit, $m ) ) { 00242 $opts['days'] = $m[1]; 00243 } 00244 if( preg_match( '/^namespace=(\d+)$/', $bit, $m ) ) { 00245 $opts['namespace'] = $m[1]; 00246 } 00247 } 00248 } 00249 00258 public function checkLastModified( $feedFormat ) { 00259 $dbr = wfGetDB( DB_SLAVE ); 00260 $lastmod = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', false, __METHOD__ ); 00261 if( $feedFormat || !$this->getUser()->useRCPatrol() ) { 00262 if( $lastmod && $this->getOutput()->checkLastModified( $lastmod ) ) { 00263 # Client cache fresh and headers sent, nothing more to do. 00264 return false; 00265 } 00266 } 00267 return $lastmod; 00268 } 00269 00276 public function buildMainQueryConds( FormOptions $opts ) { 00277 $dbr = wfGetDB( DB_SLAVE ); 00278 $conds = array(); 00279 00280 # It makes no sense to hide both anons and logged-in users 00281 # Where this occurs, force anons to be shown 00282 $forcebot = false; 00283 if( $opts['hideanons'] && $opts['hideliu'] ) { 00284 # Check if the user wants to show bots only 00285 if( $opts['hidebots'] ) { 00286 $opts['hideanons'] = false; 00287 } else { 00288 $forcebot = true; 00289 $opts['hidebots'] = false; 00290 } 00291 } 00292 00293 // Calculate cutoff 00294 $cutoff_unixtime = time() - ( $opts['days'] * 86400 ); 00295 $cutoff_unixtime = $cutoff_unixtime - ($cutoff_unixtime % 86400); 00296 $cutoff = $dbr->timestamp( $cutoff_unixtime ); 00297 00298 $fromValid = preg_match( '/^[0-9]{14}$/', $opts['from'] ); 00299 if( $fromValid && $opts['from'] > wfTimestamp( TS_MW, $cutoff ) ) { 00300 $cutoff = $dbr->timestamp( $opts['from'] ); 00301 } else { 00302 $opts->reset( 'from' ); 00303 } 00304 00305 $conds[] = 'rc_timestamp >= ' . $dbr->addQuotes( $cutoff ); 00306 00307 $hidePatrol = $this->getUser()->useRCPatrol() && $opts['hidepatrolled']; 00308 $hideLoggedInUsers = $opts['hideliu'] && !$forcebot; 00309 $hideAnonymousUsers = $opts['hideanons'] && !$forcebot; 00310 00311 if( $opts['hideminor'] ) { 00312 $conds['rc_minor'] = 0; 00313 } 00314 if( $opts['hidebots'] ) { 00315 $conds['rc_bot'] = 0; 00316 } 00317 if( $hidePatrol ) { 00318 $conds['rc_patrolled'] = 0; 00319 } 00320 if( $forcebot ) { 00321 $conds['rc_bot'] = 1; 00322 } 00323 if( $hideLoggedInUsers ) { 00324 $conds[] = 'rc_user = 0'; 00325 } 00326 if( $hideAnonymousUsers ) { 00327 $conds[] = 'rc_user != 0'; 00328 } 00329 00330 if( $opts['hidemyself'] ) { 00331 if( $this->getUser()->getId() ) { 00332 $conds[] = 'rc_user != ' . $dbr->addQuotes( $this->getUser()->getId() ); 00333 } else { 00334 $conds[] = 'rc_user_text != ' . $dbr->addQuotes( $this->getUser()->getName() ); 00335 } 00336 } 00337 00338 # Namespace filtering 00339 if( $opts['namespace'] !== '' ) { 00340 $selectedNS = $dbr->addQuotes( $opts['namespace'] ); 00341 $operator = $opts['invert'] ? '!=' : '='; 00342 $boolean = $opts['invert'] ? 'AND' : 'OR'; 00343 00344 # namespace association (bug 2429) 00345 if( !$opts['associated'] ) { 00346 $condition = "rc_namespace $operator $selectedNS"; 00347 } else { 00348 # Also add the associated namespace 00349 $associatedNS = $dbr->addQuotes( 00350 MWNamespace::getAssociated( $opts['namespace'] ) 00351 ); 00352 $condition = "(rc_namespace $operator $selectedNS " 00353 . $boolean 00354 . " rc_namespace $operator $associatedNS)"; 00355 } 00356 00357 $conds[] = $condition; 00358 } 00359 return $conds; 00360 } 00361 00369 public function doMainQuery( $conds, $opts ) { 00370 $tables = array( 'recentchanges' ); 00371 $join_conds = array(); 00372 $query_options = array( 00373 'USE INDEX' => array( 'recentchanges' => 'rc_timestamp' ) 00374 ); 00375 00376 $uid = $this->getUser()->getId(); 00377 $dbr = wfGetDB( DB_SLAVE ); 00378 $limit = $opts['limit']; 00379 $namespace = $opts['namespace']; 00380 $invert = $opts['invert']; 00381 $associated = $opts['associated']; 00382 00383 $fields = RecentChange::selectFields(); 00384 // JOIN on watchlist for users 00385 if ( $uid ) { 00386 $tables[] = 'watchlist'; 00387 $fields[] = 'wl_user'; 00388 $fields[] = 'wl_notificationtimestamp'; 00389 $join_conds['watchlist'] = array( 'LEFT JOIN', array( 00390 'wl_user' => $uid, 00391 'wl_title=rc_title', 00392 'wl_namespace=rc_namespace' 00393 )); 00394 } 00395 if ( $this->getUser()->isAllowed( 'rollback' ) ) { 00396 $tables[] = 'page'; 00397 $fields[] = 'page_latest'; 00398 $join_conds['page'] = array( 'LEFT JOIN', 'rc_cur_id=page_id' ); 00399 } 00400 // Tag stuff. 00401 ChangeTags::modifyDisplayQuery( 00402 $tables, 00403 $fields, 00404 $conds, 00405 $join_conds, 00406 $query_options, 00407 $opts['tagfilter'] 00408 ); 00409 00410 if ( !wfRunHooks( 'SpecialRecentChangesQuery', 00411 array( &$conds, &$tables, &$join_conds, $opts, &$query_options, &$fields ) ) ) 00412 { 00413 return false; 00414 } 00415 00416 // Don't use the new_namespace_time timestamp index if: 00417 // (a) "All namespaces" selected 00418 // (b) We want pages in more than one namespace (inverted/associated) 00419 // (c) There is a tag to filter on (use tag index instead) 00420 // (d) UNION + sort/limit is not an option for the DBMS 00421 if( $namespace === '' 00422 || ( $invert || $associated ) 00423 || $opts['tagfilter'] != '' 00424 || !$dbr->unionSupportsOrderAndLimit() ) 00425 { 00426 $res = $dbr->select( $tables, $fields, $conds, __METHOD__, 00427 array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit ) + 00428 $query_options, 00429 $join_conds ); 00430 // We have a new_namespace_time index! UNION over new=(0,1) and sort result set! 00431 } else { 00432 // New pages 00433 $sqlNew = $dbr->selectSQLText( 00434 $tables, 00435 $fields, 00436 array( 'rc_new' => 1 ) + $conds, 00437 __METHOD__, 00438 array( 00439 'ORDER BY' => 'rc_timestamp DESC', 00440 'LIMIT' => $limit, 00441 'USE INDEX' => array( 'recentchanges' => 'new_name_timestamp' ) 00442 ), 00443 $join_conds 00444 ); 00445 // Old pages 00446 $sqlOld = $dbr->selectSQLText( 00447 $tables, 00448 $fields, 00449 array( 'rc_new' => 0 ) + $conds, 00450 __METHOD__, 00451 array( 00452 'ORDER BY' => 'rc_timestamp DESC', 00453 'LIMIT' => $limit, 00454 'USE INDEX' => array( 'recentchanges' => 'new_name_timestamp' ) 00455 ), 00456 $join_conds 00457 ); 00458 # Join the two fast queries, and sort the result set 00459 $sql = $dbr->unionQueries( array( $sqlNew, $sqlOld ), false ) . 00460 ' ORDER BY rc_timestamp DESC'; 00461 $sql = $dbr->limitResult( $sql, $limit, false ); 00462 $res = $dbr->query( $sql, __METHOD__ ); 00463 } 00464 00465 return $res; 00466 } 00467 00474 public function webOutput( $rows, $opts ) { 00475 global $wgRCShowWatchingUsers, $wgShowUpdatedMarker, $wgAllowCategorizedRecentChanges; 00476 00477 $limit = $opts['limit']; 00478 00479 if( !$this->including() ) { 00480 // Output options box 00481 $this->doHeader( $opts ); 00482 } 00483 00484 // And now for the content 00485 $feedQuery = $this->getFeedQuery(); 00486 if ( $feedQuery !== '' ) { 00487 $this->getOutput()->setFeedAppendQuery( $feedQuery ); 00488 } else { 00489 $this->getOutput()->setFeedAppendQuery( false ); 00490 } 00491 00492 if( $wgAllowCategorizedRecentChanges ) { 00493 $this->filterByCategories( $rows, $opts ); 00494 } 00495 00496 $showWatcherCount = $wgRCShowWatchingUsers && $this->getUser()->getOption( 'shownumberswatching' ); 00497 $watcherCache = array(); 00498 00499 $dbr = wfGetDB( DB_SLAVE ); 00500 00501 $counter = 1; 00502 $list = ChangesList::newFromContext( $this->getContext() ); 00503 00504 $s = $list->beginRecentChangesList(); 00505 foreach( $rows as $obj ) { 00506 if( $limit == 0 ) { 00507 break; 00508 } 00509 $rc = RecentChange::newFromRow( $obj ); 00510 $rc->counter = $counter++; 00511 # Check if the page has been updated since the last visit 00512 if( $wgShowUpdatedMarker && !empty( $obj->wl_notificationtimestamp ) ) { 00513 $rc->notificationtimestamp = ( $obj->rc_timestamp >= $obj->wl_notificationtimestamp ); 00514 } else { 00515 $rc->notificationtimestamp = false; // Default 00516 } 00517 # Check the number of users watching the page 00518 $rc->numberofWatchingusers = 0; // Default 00519 if( $showWatcherCount && $obj->rc_namespace >= 0 ) { 00520 if( !isset( $watcherCache[$obj->rc_namespace][$obj->rc_title] ) ) { 00521 $watcherCache[$obj->rc_namespace][$obj->rc_title] = 00522 $dbr->selectField( 00523 'watchlist', 00524 'COUNT(*)', 00525 array( 00526 'wl_namespace' => $obj->rc_namespace, 00527 'wl_title' => $obj->rc_title, 00528 ), 00529 __METHOD__ . '-watchers' 00530 ); 00531 } 00532 $rc->numberofWatchingusers = $watcherCache[$obj->rc_namespace][$obj->rc_title]; 00533 } 00534 00535 $changeLine = $list->recentChangesLine( $rc, !empty( $obj->wl_user ), $counter ); 00536 if ( $changeLine !== false ) { 00537 $s .= $changeLine; 00538 --$limit; 00539 } 00540 } 00541 $s .= $list->endRecentChangesList(); 00542 $this->getOutput()->addHTML( $s ); 00543 } 00544 00550 public function getFeedQuery() { 00551 global $wgFeedLimit; 00552 00553 $this->getOptions()->validateIntBounds( 'limit', 0, $wgFeedLimit ); 00554 $options = $this->getOptions()->getChangedValues(); 00555 00556 // wfArrayToCgi() omits options set to null or false 00557 foreach ( $options as &$value ) { 00558 if ( $value === false ) { 00559 $value = '0'; 00560 } 00561 } 00562 unset( $value ); 00563 00564 return wfArrayToCgi( $options ); 00565 } 00566 00573 public function doHeader( $opts ) { 00574 global $wgScript; 00575 00576 $this->setTopText( $opts ); 00577 00578 $defaults = $opts->getAllValues(); 00579 $nondefaults = $opts->getChangedValues(); 00580 $opts->consumeValues( array( 00581 'namespace', 'invert', 'associated', 'tagfilter', 00582 'categories', 'categories_any' 00583 ) ); 00584 00585 $panel = array(); 00586 $panel[] = $this->optionsPanel( $defaults, $nondefaults ); 00587 $panel[] = '<hr />'; 00588 00589 $extraOpts = $this->getExtraOptions( $opts ); 00590 $extraOptsCount = count( $extraOpts ); 00591 $count = 0; 00592 $submit = ' ' . Xml::submitbutton( $this->msg( 'allpagessubmit' )->text() ); 00593 00594 $out = Xml::openElement( 'table', array( 'class' => 'mw-recentchanges-table' ) ); 00595 foreach( $extraOpts as $name => $optionRow ) { 00596 # Add submit button to the last row only 00597 ++$count; 00598 $addSubmit = ( $count === $extraOptsCount ) ? $submit : ''; 00599 00600 $out .= Xml::openElement( 'tr' ); 00601 if( is_array( $optionRow ) ) { 00602 $out .= Xml::tags( 'td', array( 'class' => 'mw-label mw-' . $name . '-label' ), $optionRow[0] ); 00603 $out .= Xml::tags( 'td', array( 'class' => 'mw-input' ), $optionRow[1] . $addSubmit ); 00604 } else { 00605 $out .= Xml::tags( 'td', array( 'class' => 'mw-input', 'colspan' => 2 ), $optionRow . $addSubmit ); 00606 } 00607 $out .= Xml::closeElement( 'tr' ); 00608 } 00609 $out .= Xml::closeElement( 'table' ); 00610 00611 $unconsumed = $opts->getUnconsumedValues(); 00612 foreach( $unconsumed as $key => $value ) { 00613 $out .= Html::hidden( $key, $value ); 00614 } 00615 00616 $t = $this->getTitle(); 00617 $out .= Html::hidden( 'title', $t->getPrefixedText() ); 00618 $form = Xml::tags( 'form', array( 'action' => $wgScript ), $out ); 00619 $panel[] = $form; 00620 $panelString = implode( "\n", $panel ); 00621 00622 $this->getOutput()->addHTML( 00623 Xml::fieldset( $this->msg( 'recentchanges-legend' )->text(), $panelString, array( 'class' => 'rcoptions' ) ) 00624 ); 00625 00626 $this->setBottomText( $opts ); 00627 } 00628 00635 function getExtraOptions( $opts ) { 00636 $extraOpts = array(); 00637 $extraOpts['namespace'] = $this->namespaceFilterForm( $opts ); 00638 00639 global $wgAllowCategorizedRecentChanges; 00640 if( $wgAllowCategorizedRecentChanges ) { 00641 $extraOpts['category'] = $this->categoryFilterForm( $opts ); 00642 } 00643 00644 $tagFilter = ChangeTags::buildTagFilterSelector( $opts['tagfilter'] ); 00645 if ( count( $tagFilter ) ) { 00646 $extraOpts['tagfilter'] = $tagFilter; 00647 } 00648 00649 wfRunHooks( 'SpecialRecentChangesPanel', array( &$extraOpts, $opts ) ); 00650 return $extraOpts; 00651 } 00652 00658 function setTopText( FormOptions $opts ) { 00659 global $wgContLang; 00660 00661 $message = $this->msg( 'recentchangestext' )->inContentLanguage(); 00662 if ( !$message->isDisabled() ) { 00663 $this->getOutput()->addWikiText( 00664 Html::rawElement( 'p', 00665 array( 'lang' => $wgContLang->getCode(), 'dir' => $wgContLang->getDir() ), 00666 "\n" . $message->plain() . "\n" 00667 ), 00668 /* $lineStart */ false, 00669 /* $interface */ false 00670 ); 00671 } 00672 } 00673 00680 function setBottomText( FormOptions $opts ) {} 00681 00689 protected function namespaceFilterForm( FormOptions $opts ) { 00690 $nsSelect = Html::namespaceSelector( 00691 array( 'selected' => $opts['namespace'], 'all' => '' ), 00692 array( 'name' => 'namespace', 'id' => 'namespace' ) 00693 ); 00694 $nsLabel = Xml::label( $this->msg( 'namespace' )->text(), 'namespace' ); 00695 $invert = Xml::checkLabel( 00696 $this->msg( 'invert' )->text(), 'invert', 'nsinvert', 00697 $opts['invert'], 00698 array( 'title' => $this->msg( 'tooltip-invert' )->text() ) 00699 ); 00700 $associated = Xml::checkLabel( 00701 $this->msg( 'namespace_association' )->text(), 'associated', 'nsassociated', 00702 $opts['associated'], 00703 array( 'title' => $this->msg( 'tooltip-namespace_association' )->text() ) 00704 ); 00705 return array( $nsLabel, "$nsSelect $invert $associated" ); 00706 } 00707 00714 protected function categoryFilterForm( FormOptions $opts ) { 00715 list( $label, $input ) = Xml::inputLabelSep( $this->msg( 'rc_categories' )->text(), 00716 'categories', 'mw-categories', false, $opts['categories'] ); 00717 00718 $input .= ' ' . Xml::checkLabel( $this->msg( 'rc_categories_any' )->text(), 00719 'categories_any', 'mw-categories_any', $opts['categories_any'] ); 00720 00721 return array( $label, $input ); 00722 } 00723 00730 function filterByCategories( &$rows, FormOptions $opts ) { 00731 $categories = array_map( 'trim', explode( '|', $opts['categories'] ) ); 00732 00733 if( !count( $categories ) ) { 00734 return; 00735 } 00736 00737 # Filter categories 00738 $cats = array(); 00739 foreach( $categories as $cat ) { 00740 $cat = trim( $cat ); 00741 if( $cat == '' ) { 00742 continue; 00743 } 00744 $cats[] = $cat; 00745 } 00746 00747 # Filter articles 00748 $articles = array(); 00749 $a2r = array(); 00750 $rowsarr = array(); 00751 foreach( $rows as $k => $r ) { 00752 $nt = Title::makeTitle( $r->rc_namespace, $r->rc_title ); 00753 $id = $nt->getArticleID(); 00754 if( $id == 0 ) { 00755 continue; # Page might have been deleted... 00756 } 00757 if( !in_array( $id, $articles ) ) { 00758 $articles[] = $id; 00759 } 00760 if( !isset( $a2r[$id] ) ) { 00761 $a2r[$id] = array(); 00762 } 00763 $a2r[$id][] = $k; 00764 $rowsarr[$k] = $r; 00765 } 00766 00767 # Shortcut? 00768 if( !count( $articles ) || !count( $cats ) ) { 00769 return; 00770 } 00771 00772 # Look up 00773 $c = new Categoryfinder; 00774 $c->seed( $articles, $cats, $opts['categories_any'] ? 'OR' : 'AND' ); 00775 $match = $c->run(); 00776 00777 # Filter 00778 $newrows = array(); 00779 foreach( $match as $id ) { 00780 foreach( $a2r[$id] as $rev ) { 00781 $k = $rev; 00782 $newrows[$k] = $rowsarr[$k]; 00783 } 00784 } 00785 $rows = $newrows; 00786 } 00787 00797 function makeOptionsLink( $title, $override, $options, $active = false ) { 00798 $params = $override + $options; 00799 00800 // Bug 36524: false values have be converted to "0" otherwise 00801 // wfArrayToCgi() will omit it them. 00802 foreach ( $params as &$value ) { 00803 if ( $value === false ) { 00804 $value = '0'; 00805 } 00806 } 00807 unset( $value ); 00808 00809 $text = htmlspecialchars( $title ); 00810 if ( $active ) { 00811 $text = '<strong>' . $text . '</strong>'; 00812 } 00813 return Linker::linkKnown( $this->getTitle(), $text, array(), $params ); 00814 } 00815 00823 function optionsPanel( $defaults, $nondefaults ) { 00824 global $wgRCLinkLimits, $wgRCLinkDays; 00825 00826 $options = $nondefaults + $defaults; 00827 00828 $note = ''; 00829 $msg = $this->msg( 'rclegend' ); 00830 if( !$msg->isDisabled() ) { 00831 $note .= '<div class="mw-rclegend">' . $msg->parse() . "</div>\n"; 00832 } 00833 00834 $lang = $this->getLanguage(); 00835 $user = $this->getUser(); 00836 if( $options['from'] ) { 00837 $note .= $this->msg( 'rcnotefrom' )->numParams( $options['limit'] )->params( 00838 $lang->userTimeAndDate( $options['from'], $user ), 00839 $lang->userDate( $options['from'], $user ), 00840 $lang->userTime( $options['from'], $user ) )->parse() . '<br />'; 00841 } 00842 00843 # Sort data for display and make sure it's unique after we've added user data. 00844 $wgRCLinkLimits[] = $options['limit']; 00845 $wgRCLinkDays[] = $options['days']; 00846 sort( $wgRCLinkLimits ); 00847 sort( $wgRCLinkDays ); 00848 $wgRCLinkLimits = array_unique( $wgRCLinkLimits ); 00849 $wgRCLinkDays = array_unique( $wgRCLinkDays ); 00850 00851 // limit links 00852 foreach( $wgRCLinkLimits as $value ) { 00853 $cl[] = $this->makeOptionsLink( $lang->formatNum( $value ), 00854 array( 'limit' => $value ), $nondefaults, $value == $options['limit'] ); 00855 } 00856 $cl = $lang->pipeList( $cl ); 00857 00858 // day links, reset 'from' to none 00859 foreach( $wgRCLinkDays as $value ) { 00860 $dl[] = $this->makeOptionsLink( $lang->formatNum( $value ), 00861 array( 'days' => $value, 'from' => '' ), $nondefaults, $value == $options['days'] ); 00862 } 00863 $dl = $lang->pipeList( $dl ); 00864 00865 // show/hide links 00866 $showhide = array( $this->msg( 'show' )->text(), $this->msg( 'hide' )->text() ); 00867 $filters = array( 00868 'hideminor' => 'rcshowhideminor', 00869 'hidebots' => 'rcshowhidebots', 00870 'hideanons' => 'rcshowhideanons', 00871 'hideliu' => 'rcshowhideliu', 00872 'hidepatrolled' => 'rcshowhidepatr', 00873 'hidemyself' => 'rcshowhidemine' 00874 ); 00875 foreach ( $this->getCustomFilters() as $key => $params ) { 00876 $filters[$key] = $params['msg']; 00877 } 00878 // Disable some if needed 00879 if ( !$user->useRCPatrol() ) { 00880 unset( $filters['hidepatrolled'] ); 00881 } 00882 00883 $links = array(); 00884 foreach ( $filters as $key => $msg ) { 00885 $link = $this->makeOptionsLink( $showhide[1 - $options[$key]], 00886 array( $key => 1-$options[$key] ), $nondefaults ); 00887 $links[] = $this->msg( $msg )->rawParams( $link )->escaped(); 00888 } 00889 00890 // show from this onward link 00891 $timestamp = wfTimestampNow(); 00892 $now = $lang->userTimeAndDate( $timestamp, $user ); 00893 $tl = $this->makeOptionsLink( 00894 $now, array( 'from' => $timestamp ), $nondefaults 00895 ); 00896 00897 $rclinks = $this->msg( 'rclinks' )->rawParams( $cl, $dl, $lang->pipeList( $links ) )->parse(); 00898 $rclistfrom = $this->msg( 'rclistfrom' )->rawParams( $tl )->parse(); 00899 return "{$note}$rclinks<br />$rclistfrom"; 00900 } 00901 00905 function addRecentChangesJS() { 00906 $this->getOutput()->addModules( array( 00907 'mediawiki.special.recentchanges', 00908 ) ); 00909 } 00910 00911 protected function getGroupName() { 00912 return 'changes'; 00913 } 00914 }