PageRenderTime 219ms CodeModel.GetById 81ms app.highlight 74ms RepoModel.GetById 56ms app.codeStats 1ms

/includes/specials/SpecialRecentchanges.php

https://github.com/tav/confluence
PHP | 680 lines | 436 code | 91 blank | 153 comment | 81 complexity | 6cc27969cb1970ed56141130a24791ed MD5 | raw file
  1<?php
  2
  3/**
  4 * Implements Special:Recentchanges
  5 * @ingroup SpecialPage
  6 */
  7class SpecialRecentChanges extends SpecialPage {
  8	public function __construct() {
  9  		parent::__construct( 'Recentchanges' );
 10		$this->includable( true );
 11	}
 12
 13	/**
 14	 * Get a FormOptions object containing the default options
 15	 *
 16	 * @return FormOptions
 17	 */
 18	public function getDefaultOptions() {
 19		global $wgUser;
 20		$opts = new FormOptions();
 21
 22		$opts->add( 'days',  (int)$wgUser->getOption( 'rcdays' ) );
 23		$opts->add( 'limit', (int)$wgUser->getOption( 'rclimit' ) );
 24		$opts->add( 'from', '' );
 25
 26		$opts->add( 'hideminor',     $wgUser->getBoolOption( 'hideminor' ) );
 27		$opts->add( 'hidebots',      true  );
 28		$opts->add( 'hideanons',     false );
 29		$opts->add( 'hideliu',       false );
 30		$opts->add( 'hidepatrolled', $wgUser->getBoolOption( 'hidepatrolled' ) );
 31		$opts->add( 'hidemyself',    false );
 32
 33		$opts->add( 'namespace', '', FormOptions::INTNULL );
 34		$opts->add( 'invert', false );
 35
 36		$opts->add( 'categories', '' );
 37		$opts->add( 'categories_any', false );
 38		$opts->add( 'tagfilter', '' );
 39		return $opts;
 40	}
 41
 42	/**
 43	 * Get a FormOptions object with options as specified by the user
 44	 *
 45	 * @return FormOptions
 46	 */
 47	public function setup( $parameters ) {
 48		global $wgRequest;
 49
 50		$opts = $this->getDefaultOptions();
 51		$opts->fetchValuesFromRequest( $wgRequest );
 52
 53		// Give precedence to subpage syntax
 54		if( $parameters !== null ) {
 55			$this->parseParameters( $parameters, $opts );
 56		}
 57
 58		$opts->validateIntBounds( 'limit', 0, 500 );
 59		return $opts;
 60	}
 61
 62	/**
 63	 * Get a FormOptions object sepcific for feed requests
 64	 *
 65	 * @return FormOptions
 66	 */
 67	public function feedSetup() {
 68		global $wgFeedLimit, $wgRequest;
 69		$opts = $this->getDefaultOptions();
 70		# Feed is cached on limit,hideminor; other params would randomly not work
 71		$opts->fetchValuesFromRequest( $wgRequest, array( 'limit', 'hideminor' ) );
 72		$opts->validateIntBounds( 'limit', 0, $wgFeedLimit );
 73		return $opts;
 74	}
 75
 76	/**
 77	 * Main execution point
 78	 *
 79	 * @param $parameters string
 80	 */
 81	public function execute( $parameters ) {
 82		global $wgRequest, $wgOut;
 83		$feedFormat = $wgRequest->getVal( 'feed' );
 84
 85		# 10 seconds server-side caching max
 86		$wgOut->setSquidMaxage( 10 );
 87		# Check if the client has a cached version
 88		$lastmod = $this->checkLastModified( $feedFormat );
 89		if( $lastmod === false ) {
 90			return;
 91		}
 92
 93		$opts = $feedFormat ? $this->feedSetup() : $this->setup( $parameters );
 94		$this->setHeaders();
 95		$this->outputHeader();
 96
 97		// Fetch results, prepare a batch link existence check query
 98		$rows = array();
 99		$conds = $this->buildMainQueryConds( $opts );
100		$rows = $this->doMainQuery( $conds, $opts );
101		if( $rows === false ){
102			if( !$this->including() ) {
103				$this->doHeader( $opts );
104			}
105			return;
106		}
107
108		if( !$feedFormat ) {
109			$batch = new LinkBatch;
110			foreach( $rows as $row ) {
111				$batch->add( NS_USER, $row->rc_user_text  );
112				$batch->add( NS_USER_TALK, $row->rc_user_text  );
113				$batch->add( $row->rc_namespace, $row->rc_title );
114			}
115			$batch->execute();
116		}
117		$target = isset($opts['target']) ? $opts['target'] : ''; // RCL has targets
118		if( $feedFormat ) {
119			list( $feed, $feedObj ) = $this->getFeedObject( $feedFormat );
120			$feed->execute( $feedObj, $rows, $opts['limit'], $opts['hideminor'], $lastmod, $target );
121		} else {
122			$this->webOutput( $rows, $opts );
123		}
124
125		$rows->free();
126	}
127
128	/**
129	 * Return an array with a ChangesFeed object and ChannelFeed object
130	 *
131	 * @return array
132	 */
133	public function getFeedObject( $feedFormat ){
134		$feed = new ChangesFeed( $feedFormat, 'rcfeed' );
135		$feedObj = $feed->getFeedObject(
136			wfMsgForContent( 'recentchanges' ),
137			wfMsgForContent( 'recentchanges-feed-description' )
138		);
139		return array( $feed, $feedObj );
140	}
141
142	/**
143	 * Process $par and put options found if $opts
144	 * Mainly used when including the page
145	 *
146	 * @param $par String
147	 * @param $opts FormOptions
148	 */
149	public function parseParameters( $par, FormOptions $opts ) {
150		$bits = preg_split( '/\s*,\s*/', trim( $par ) );
151		foreach( $bits as $bit ) {
152			if( 'hidebots' === $bit ) $opts['hidebots'] = true;
153			if( 'bots' === $bit ) $opts['hidebots'] = false;
154			if( 'hideminor' === $bit ) $opts['hideminor'] = true;
155			if( 'minor' === $bit ) $opts['hideminor'] = false;
156			if( 'hideliu' === $bit ) $opts['hideliu'] = true;
157			if( 'hidepatrolled' === $bit ) $opts['hidepatrolled'] = true;
158			if( 'hideanons' === $bit ) $opts['hideanons'] = true;
159			if( 'hidemyself' === $bit ) $opts['hidemyself'] = true;
160
161			if( is_numeric( $bit ) ) $opts['limit'] =  $bit;
162
163			$m = array();
164			if( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) $opts['limit'] = $m[1];
165			if( preg_match( '/^days=(\d+)$/', $bit, $m ) ) $opts['days'] = $m[1];
166		}
167	}
168
169	/**
170	 * Get last modified date, for client caching
171	 * Don't use this if we are using the patrol feature, patrol changes don't
172	 * update the timestamp
173	 *
174	 * @param $feedFormat String
175	 * @return string or false
176	 */
177	public function checkLastModified( $feedFormat ) {
178		global $wgUseRCPatrol, $wgOut;
179		$dbr = wfGetDB( DB_SLAVE );
180		$lastmod = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', false, __FUNCTION__ );
181		if( $feedFormat || !$wgUseRCPatrol ) {
182			if( $lastmod && $wgOut->checkLastModified( $lastmod ) ) {
183				# Client cache fresh and headers sent, nothing more to do.
184				return false;
185			}
186		}
187		return $lastmod;
188	}
189
190	/**
191	 * Return an array of conditions depending of options set in $opts
192	 *
193	 * @param $opts FormOptions
194	 * @return array
195	 */
196	public function buildMainQueryConds( FormOptions $opts ) {
197		global $wgUser;
198
199		$dbr = wfGetDB( DB_SLAVE );
200		$conds = array();
201
202		# It makes no sense to hide both anons and logged-in users
203		# Where this occurs, force anons to be shown
204		$forcebot = false;
205		if( $opts['hideanons'] && $opts['hideliu'] ){
206			# Check if the user wants to show bots only
207			if( $opts['hidebots'] ){
208				$opts['hideanons'] = false;
209			} else {
210				$forcebot = true;
211				$opts['hidebots'] = false;
212			}
213		}
214
215		// Calculate cutoff
216		$cutoff_unixtime = time() - ( $opts['days'] * 86400 );
217		$cutoff_unixtime = $cutoff_unixtime - ($cutoff_unixtime % 86400);
218		$cutoff = $dbr->timestamp( $cutoff_unixtime );
219
220		$fromValid = preg_match('/^[0-9]{14}$/', $opts['from']);
221		if( $fromValid && $opts['from'] > wfTimestamp(TS_MW,$cutoff) ) {
222			$cutoff = $dbr->timestamp($opts['from']);
223		} else {
224			$opts->reset( 'from' );
225		}
226
227		$conds[] = 'rc_timestamp >= ' . $dbr->addQuotes( $cutoff );
228
229
230		$hidePatrol = $wgUser->useRCPatrol() && $opts['hidepatrolled'];
231		$hideLoggedInUsers = $opts['hideliu'] && !$forcebot;
232		$hideAnonymousUsers = $opts['hideanons'] && !$forcebot;
233
234		if( $opts['hideminor'] )  $conds['rc_minor'] = 0;
235		if( $opts['hidebots'] )   $conds['rc_bot'] = 0;
236		if( $hidePatrol )         $conds['rc_patrolled'] = 0;
237		if( $forcebot )           $conds['rc_bot'] = 1;
238		if( $hideLoggedInUsers )  $conds[] = 'rc_user = 0';
239		if( $hideAnonymousUsers ) $conds[] = 'rc_user != 0';
240
241		if( $opts['hidemyself'] ) {
242			if( $wgUser->getId() ) {
243				$conds[] = 'rc_user != ' . $dbr->addQuotes( $wgUser->getId() );
244			} else {
245				$conds[] = 'rc_user_text != ' . $dbr->addQuotes( $wgUser->getName() );
246			}
247		}
248
249		# Namespace filtering
250		if( $opts['namespace'] !== '' ) {
251			if( !$opts['invert'] ) {
252				$conds[] = 'rc_namespace = ' . $dbr->addQuotes( $opts['namespace'] );
253			} else {
254				$conds[] = 'rc_namespace != ' . $dbr->addQuotes( $opts['namespace'] );
255			}
256		}
257
258		return $conds;
259	}
260
261	/**
262	 * Process the query
263	 *
264	 * @param $conds array
265	 * @param $opts FormOptions
266	 * @return database result or false (for Recentchangeslinked only)
267	 */
268	public function doMainQuery( $conds, $opts ) {
269		global $wgUser;
270
271		$tables = array( 'recentchanges' );
272		$join_conds = array();
273		$query_options = array( 'USE INDEX' => array('recentchanges' => 'rc_timestamp') );
274
275		$uid = $wgUser->getId();
276		$dbr = wfGetDB( DB_SLAVE );
277		$limit = $opts['limit'];
278		$namespace = $opts['namespace'];
279		$invert = $opts['invert'];
280
281		$join_conds = array();
282
283		// JOIN on watchlist for users
284		if( $uid ) {
285			$tables[] = 'watchlist';
286			$join_conds['watchlist'] = array('LEFT JOIN',
287				"wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace");
288		}
289		if ($wgUser->isAllowed("rollback")) {
290			$tables[] = 'page';
291			$join_conds['page'] = array('LEFT JOIN', 'rc_cur_id=page_id');
292		}
293		// Tag stuff.
294		$fields = array();
295		// Fields are * in this case, so let the function modify an empty array to keep it happy.
296		ChangeTags::modifyDisplayQuery( $tables,
297										$fields,
298										$conds,
299										$join_conds,
300										$query_options,
301										$opts['tagfilter']
302									);
303
304		wfRunHooks('SpecialRecentChangesQuery', array( &$conds, &$tables, &$join_conds, $opts ) );
305
306		// Is there either one namespace selected or excluded?
307		// Tag filtering also has a better index.
308		// Also, if this is "all" or main namespace, just use timestamp index.
309		if( is_null($namespace) || $invert || $namespace == NS_MAIN || $opts['tagfilter'] ) {
310			$res = $dbr->select( $tables, '*', $conds, __METHOD__,
311				array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit ) +
312				$query_options,
313				$join_conds );
314		// We have a new_namespace_time index! UNION over new=(0,1) and sort result set!
315		} else {
316			// New pages
317			$sqlNew = $dbr->selectSQLText( $tables, '*',
318				array( 'rc_new' => 1 ) + $conds,
319				__METHOD__,
320				array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit,
321					'USE INDEX' =>  array('recentchanges' => 'new_name_timestamp') ),
322				$join_conds );
323			// Old pages
324			$sqlOld = $dbr->selectSQLText( $tables, '*',
325				array( 'rc_new' => 0 ) + $conds,
326				__METHOD__,
327				array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit,
328					'USE INDEX' =>  array('recentchanges' => 'new_name_timestamp') ),
329				$join_conds );
330			# Join the two fast queries, and sort the result set
331			$sql = "($sqlNew) UNION ($sqlOld) ORDER BY rc_timestamp DESC LIMIT $limit";
332			$res = $dbr->query( $sql, __METHOD__ );
333		}
334
335		return $res;
336	}
337
338	/**
339	 * Send output to $wgOut, only called if not used feeds
340	 *
341	 * @param $rows array of database rows
342	 * @param $opts FormOptions
343	 */
344	public function webOutput( $rows, $opts ) {
345		global $wgOut, $wgUser, $wgRCShowWatchingUsers, $wgShowUpdatedMarker;
346		global $wgAllowCategorizedRecentChanges;
347
348		$limit = $opts['limit'];
349
350		if( !$this->including() ) {
351			// Output options box
352			$this->doHeader( $opts );
353		}
354
355		// And now for the content
356		$wgOut->setSyndicated( true );
357
358		if( $wgAllowCategorizedRecentChanges ) {
359			$this->filterByCategories( $rows, $opts );
360		}
361
362		$showWatcherCount = $wgRCShowWatchingUsers && $wgUser->getOption( 'shownumberswatching' );
363		$watcherCache = array();
364
365		$dbr = wfGetDB( DB_SLAVE );
366
367		$counter = 1;
368		$list = ChangesList::newFromUser( $wgUser );
369
370		$s = $list->beginRecentChangesList();
371		foreach( $rows as $obj ) {
372			if( $limit == 0 ) break;
373			$rc = RecentChange::newFromRow( $obj );
374			$rc->counter = $counter++;
375			# Check if the page has been updated since the last visit
376			if( $wgShowUpdatedMarker && !empty($obj->wl_notificationtimestamp) ) {
377				$rc->notificationtimestamp = ($obj->rc_timestamp >= $obj->wl_notificationtimestamp);
378			} else {
379				$rc->notificationtimestamp = false; // Default
380			}
381			# Check the number of users watching the page
382			$rc->numberofWatchingusers = 0; // Default
383			if( $showWatcherCount && $obj->rc_namespace >= 0 ) {
384				if( !isset($watcherCache[$obj->rc_namespace][$obj->rc_title]) ) {
385					$watcherCache[$obj->rc_namespace][$obj->rc_title] =
386						 $dbr->selectField( 'watchlist',
387							'COUNT(*)',
388							array(
389								'wl_namespace' => $obj->rc_namespace,
390								'wl_title' => $obj->rc_title,
391							),
392							__METHOD__ . '-watchers' );
393				}
394				$rc->numberofWatchingusers = $watcherCache[$obj->rc_namespace][$obj->rc_title];
395			}
396			$s .= $list->recentChangesLine( $rc, !empty( $obj->wl_user ), $counter );
397			--$limit;
398		}
399		$s .= $list->endRecentChangesList();
400		$wgOut->addHTML( $s );
401	}
402
403	/**
404	 * Return the text to be displayed above the changes
405	 *
406	 * @param $opts FormOptions
407	 * @return String: XHTML
408	 */
409	public function doHeader( $opts ) {
410		global $wgScript, $wgOut;
411
412		$this->setTopText( $wgOut, $opts );
413
414		$defaults = $opts->getAllValues();
415		$nondefaults = $opts->getChangedValues();
416		$opts->consumeValues( array( 'namespace', 'invert' ) );
417
418		$panel = array();
419		$panel[] = $this->optionsPanel( $defaults, $nondefaults );
420		$panel[] = '<hr />';
421
422		$extraOpts = $this->getExtraOptions( $opts );
423		$extraOptsCount = count( $extraOpts );
424		$count = 0;
425		$submit = ' ' . Xml::submitbutton( wfMsg( 'allpagessubmit' ) );
426
427		$out = Xml::openElement( 'table', array( 'class' => 'mw-recentchanges-table' ) );
428		foreach( $extraOpts as $optionRow ) {
429			# Add submit button to the last row only
430			++$count;
431			$addSubmit = $count === $extraOptsCount ? $submit : '';
432
433			$out .= Xml::openElement( 'tr' );
434			if( is_array( $optionRow ) ) {
435				$out .= Xml::tags( 'td', array( 'class' => 'mw-label' ), $optionRow[0] );
436				$out .= Xml::tags( 'td', array( 'class' => 'mw-input' ), $optionRow[1] . $addSubmit );
437			} else {
438				$out .= Xml::tags( 'td', array( 'class' => 'mw-input', 'colspan' => 2 ), $optionRow . $addSubmit );
439			}
440			$out .= Xml::closeElement( 'tr' );
441		}
442		$out .= Xml::closeElement( 'table' );
443
444		$unconsumed = $opts->getUnconsumedValues();
445		foreach( $unconsumed as $key => $value ) {
446			$out .= Xml::hidden( $key, $value );
447		}
448
449		$t = $this->getTitle();
450		$out .= Xml::hidden( 'title', $t->getPrefixedText() );
451		$form = Xml::tags( 'form', array( 'action' => $wgScript ), $out );
452		$panel[] = $form;
453		$panelString = implode( "\n", $panel );
454
455		$wgOut->addHTML(
456			Xml::fieldset( wfMsg( 'recentchanges-legend' ), $panelString, array( 'class' => 'rcoptions' ) )
457		);
458
459		$this->setBottomText( $wgOut, $opts );
460	}
461
462	/**
463	 * Get options to be displayed in a form
464	 *
465	 * @param $opts FormOptions
466	 * @return array
467	 */
468	function getExtraOptions( $opts ){
469		$extraOpts = array();
470		$extraOpts['namespace'] = $this->namespaceFilterForm( $opts );
471
472		global $wgAllowCategorizedRecentChanges;
473		if( $wgAllowCategorizedRecentChanges ) {
474			$extraOpts['category'] = $this->categoryFilterForm( $opts );
475		}
476
477		$tagFilter = ChangeTags::buildTagFilterSelector( $opts['tagfilter'] );
478		if ( count($tagFilter) )
479			$extraOpts['tagfilter'] = $tagFilter;
480
481		wfRunHooks( 'SpecialRecentChangesPanel', array( &$extraOpts, $opts ) );
482		return $extraOpts;
483	}
484
485	/**
486	 * Send the text to be displayed above the options
487	 *
488	 * @param $out OutputPage
489	 * @param $opts FormOptions
490	 */
491	function setTopText( OutputPage $out, FormOptions $opts ){
492		$out->addWikiText( wfMsgForContentNoTrans( 'recentchangestext' ) );
493	}
494
495	/**
496	 * Send the text to be displayed after the options, for use in
497	 * Recentchangeslinked
498	 *
499	 * @param $out OutputPage
500	 * @param $opts FormOptions
501	 */
502	function setBottomText( OutputPage $out, FormOptions $opts ){}
503
504	/**
505	 * Creates the choose namespace selection
506	 *
507	 * @param $opts FormOptions
508	 * @return string
509	 */
510	protected function namespaceFilterForm( FormOptions $opts ) {
511		$nsSelect = Xml::namespaceSelector( $opts['namespace'], '' );
512		$nsLabel = Xml::label( wfMsg('namespace'), 'namespace' );
513		$invert = Xml::checkLabel( wfMsg('invert'), 'invert', 'nsinvert', $opts['invert'] );
514		return array( $nsLabel, "$nsSelect $invert" );
515	}
516
517	/**
518	 * Create a input to filter changes by categories
519	 *
520	 * @param $opts FormOptions
521	 * @return array
522	 */
523	protected function categoryFilterForm( FormOptions $opts ) {
524		list( $label, $input ) = Xml::inputLabelSep( wfMsg('rc_categories'),
525			'categories', 'mw-categories', false, $opts['categories'] );
526
527		$input .= ' ' . Xml::checkLabel( wfMsg('rc_categories_any'),
528			'categories_any', 'mw-categories_any', $opts['categories_any'] );
529
530		return array( $label, $input );
531	}
532
533	/**
534	 * Filter $rows by categories set in $opts
535	 *
536	 * @param $rows array of database rows
537	 * @param $opts FormOptions
538	 */
539	function filterByCategories( &$rows, FormOptions $opts ) {
540		$categories = array_map( 'trim', explode( "|" , $opts['categories'] ) );
541
542		if( empty($categories) ) {
543			return;
544		}
545
546		# Filter categories
547		$cats = array();
548		foreach( $categories as $cat ) {
549			$cat = trim( $cat );
550			if( $cat == "" ) continue;
551			$cats[] = $cat;
552		}
553
554		# Filter articles
555		$articles = array();
556		$a2r = array();
557		foreach( $rows AS $k => $r ) {
558			$nt = Title::makeTitle( $r->rc_namespace, $r->rc_title );
559			$id = $nt->getArticleID();
560			if( $id == 0 ) continue; # Page might have been deleted...
561			if( !in_array($id, $articles) ) {
562				$articles[] = $id;
563			}
564			if( !isset($a2r[$id]) ) {
565				$a2r[$id] = array();
566			}
567			$a2r[$id][] = $k;
568		}
569
570		# Shortcut?
571		if( !count($articles) || !count($cats) )
572			return ;
573
574		# Look up
575		$c = new Categoryfinder ;
576		$c->seed( $articles, $cats, $opts['categories_any'] ? "OR" : "AND" ) ;
577		$match = $c->run();
578
579		# Filter
580		$newrows = array();
581		foreach( $match AS $id ) {
582			foreach( $a2r[$id] AS $rev ) {
583				$k = $rev;
584				$newrows[$k] = $rows[$k];
585			}
586		}
587		$rows = $newrows;
588	}
589
590	/**
591	 * Makes change an option link which carries all the other options
592	 * @param $title see Title
593	 * @param $override
594	 * @param $options
595	 */
596	function makeOptionsLink( $title, $override, $options, $active = false ) {
597		global $wgUser;
598		$sk = $wgUser->getSkin();
599		$params = $override + $options;
600		return $sk->link( $this->getTitle(), htmlspecialchars( $title ),
601			( $active ? array( 'style'=>'font-weight: bold;' ) : array() ), $params, array( 'known' ) );
602	}
603
604	/**
605	 * Creates the options panel.
606	 * @param $defaults array
607	 * @param $nondefaults array
608	 */
609	function optionsPanel( $defaults, $nondefaults ) {
610		global $wgLang, $wgUser, $wgRCLinkLimits, $wgRCLinkDays;
611
612		$options = $nondefaults + $defaults;
613
614		$note = '';
615		if( !wfEmptyMsg( 'rclegend', wfMsg('rclegend') ) ) {
616			$note .= '<div class="mw-rclegend">' . wfMsgExt( 'rclegend', array('parseinline') ) . "</div>\n";
617		}
618		if( $options['from'] ) {
619			$note .= wfMsgExt( 'rcnotefrom', array( 'parseinline' ),
620				$wgLang->formatNum( $options['limit'] ),
621				$wgLang->timeanddate( $options['from'], true ) ) . '<br />';
622		}
623
624		# Sort data for display and make sure it's unique after we've added user data.
625		$wgRCLinkLimits[] = $options['limit'];
626		$wgRCLinkDays[] = $options['days'];
627		sort( $wgRCLinkLimits );
628		sort( $wgRCLinkDays );
629		$wgRCLinkLimits = array_unique( $wgRCLinkLimits );
630		$wgRCLinkDays = array_unique( $wgRCLinkDays );
631
632		// limit links
633		foreach( $wgRCLinkLimits as $value ) {
634			$cl[] = $this->makeOptionsLink( $wgLang->formatNum( $value ),
635				array( 'limit' => $value ), $nondefaults, $value == $options['limit'] ) ;
636		}
637		$cl = $wgLang->pipeList( $cl );
638
639		// day links, reset 'from' to none
640		foreach( $wgRCLinkDays as $value ) {
641			$dl[] = $this->makeOptionsLink( $wgLang->formatNum( $value ),
642				array( 'days' => $value, 'from' => '' ), $nondefaults, $value == $options['days'] ) ;
643		}
644		$dl = $wgLang->pipeList( $dl );
645
646
647		// show/hide links
648		$showhide = array( wfMsg( 'show' ), wfMsg( 'hide' ) );
649		$minorLink = $this->makeOptionsLink( $showhide[1-$options['hideminor']],
650			array( 'hideminor' => 1-$options['hideminor'] ), $nondefaults);
651		$botLink = $this->makeOptionsLink( $showhide[1-$options['hidebots']],
652			array( 'hidebots' => 1-$options['hidebots'] ), $nondefaults);
653		$anonsLink = $this->makeOptionsLink( $showhide[ 1 - $options['hideanons'] ],
654			array( 'hideanons' => 1 - $options['hideanons'] ), $nondefaults );
655		$liuLink   = $this->makeOptionsLink( $showhide[1-$options['hideliu']],
656			array( 'hideliu' => 1-$options['hideliu'] ), $nondefaults);
657		$patrLink  = $this->makeOptionsLink( $showhide[1-$options['hidepatrolled']],
658			array( 'hidepatrolled' => 1-$options['hidepatrolled'] ), $nondefaults);
659		$myselfLink = $this->makeOptionsLink( $showhide[1-$options['hidemyself']],
660			array( 'hidemyself' => 1-$options['hidemyself'] ), $nondefaults);
661
662		$links[] = wfMsgHtml( 'rcshowhideminor', $minorLink );
663		$links[] = wfMsgHtml( 'rcshowhidebots', $botLink );
664		$links[] = wfMsgHtml( 'rcshowhideanons', $anonsLink );
665		$links[] = wfMsgHtml( 'rcshowhideliu', $liuLink );
666		if( $wgUser->useRCPatrol() )
667			$links[] = wfMsgHtml( 'rcshowhidepatr', $patrLink );
668		$links[] = wfMsgHtml( 'rcshowhidemine', $myselfLink );
669		$hl = $wgLang->pipeList( $links );
670
671		// show from this onward link
672		$now = $wgLang->timeanddate( wfTimestampNow(), true );
673		$tl =  $this->makeOptionsLink( $now, array( 'from' => wfTimestampNow() ), $nondefaults );
674
675		$rclinks = wfMsgExt( 'rclinks', array( 'parseinline', 'replaceafter' ),
676			$cl, $dl, $hl );
677		$rclistfrom = wfMsgExt( 'rclistfrom', array( 'parseinline', 'replaceafter' ), $tl );
678		return "{$note}$rclinks<br />$rclistfrom";
679	}
680}