MediaWiki  REL1_22
SpecialRecentchanges.php
Go to the documentation of this file.
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 }