$this->getConfig()->get( 'StructuredChangeFiltersEnableExperimentalViews' );
$out->addJsConfigVars( 'wgStructuredChangeFilters', $jsData['groups'] );
- $out->addJsConfigVars(
- 'wgStructuredChangeFiltersEnableSaving',
- $this->getConfig()->get( 'StructuredChangeFiltersEnableSaving' )
- );
$out->addJsConfigVars(
'wgStructuredChangeFiltersEnableExperimentalViews',
$experimentalStructuredChangeFilters
'wgStructuredChangeFiltersEnableLiveUpdate',
$this->getConfig()->get( 'StructuredChangeFiltersEnableLiveUpdate' )
);
- if ( $experimentalStructuredChangeFilters ) {
- $out->addJsConfigVars(
- 'wgRCFiltersChangeTags',
- $this->buildChangeTagList()
- );
- }
+ $out->addJsConfigVars(
+ 'wgRCFiltersChangeTags',
+ $this->buildChangeTagList()
+ );
+ $out->addJsConfigVars(
+ 'StructuredChangeFiltersDisplayConfig',
+ [
+ 'maxDays' => (int)$this->getConfig()->get( 'RCMaxAge' ) / ( 24 * 3600 ), // Translate to days
+ 'limitArray' => $this->getConfig()->get( 'RCLinkLimits' ),
+ 'daysArray' => $this->getConfig()->get( 'RCLinkDays' ),
+ ]
+ );
}
}
$opts = parent::getDefaultOptions();
$user = $this->getUser();
- $opts->add( 'days', $user->getIntOption( 'rcdays' ) );
+ $opts->add( 'days', $user->getIntOption( 'rcdays' ), FormOptions::FLOAT );
$opts->add( 'limit', $user->getIntOption( 'rclimit' ) );
$opts->add( 'from', '' );
if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) {
$opts['limit'] = $m[1];
}
- if ( preg_match( '/^days=(\d+)$/', $bit, $m ) ) {
+ if ( preg_match( '/^days=(\d+(?:\.\d+)?)$/', $bit, $m ) ) {
$opts['days'] = $m[1];
}
if ( preg_match( '/^namespace=(.*)$/', $bit, $m ) ) {
public function validateOptions( FormOptions $opts ) {
$opts->validateIntBounds( 'limit', 0, 5000 );
+ $opts->validateBounds( 'days', 0, $this->getConfig()->get( 'RCMaxAge' ) / ( 3600 * 24 ) );
parent::validateOptions( $opts );
}
$query_options, $join_conds, $opts );
// Calculate cutoff
- $cutoff_unixtime = time() - ( $opts['days'] * 86400 );
- $cutoff_unixtime = $cutoff_unixtime - ( $cutoff_unixtime % 86400 );
+ $cutoff_unixtime = time() - $opts['days'] * 3600 * 24;
$cutoff = $dbr->timestamp( $cutoff_unixtime );
$fromValid = preg_match( '/^[0-9]{14}$/', $opts['from'] );
$fields[] = 'page_latest';
$join_conds['page'] = [ 'LEFT JOIN', 'rc_cur_id=page_id' ];
+ $tagFilter = $opts['tagfilter'] ? explode( '|', $opts['tagfilter'] ) : [];
ChangeTags::modifyDisplayQuery(
$tables,
$fields,
$conds,
$join_conds,
$query_options,
- $opts['tagfilter']
+ $tagFilter
);
if ( !$this->runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds,
return false;
}
+ $orderByAndLimit = [
+ 'ORDER BY' => 'rc_timestamp DESC',
+ 'LIMIT' => $opts['limit']
+ ];
+ if ( in_array( 'DISTINCT', $query_options ) ) {
+ // ChangeTags::modifyDisplayQuery() adds DISTINCT when filtering on multiple tags.
+ // In order to prevent DISTINCT from causing query performance problems,
+ // we have to GROUP BY the primary key. This in turn requires us to add
+ // the primary key to the end of the ORDER BY, and the old ORDER BY to the
+ // start of the GROUP BY
+ $orderByAndLimit['ORDER BY'] = 'rc_timestamp DESC, rc_id DESC';
+ $orderByAndLimit['GROUP BY'] = 'rc_timestamp, rc_id';
+ }
// array_merge() is used intentionally here so that hooks can, should
// they so desire, override the ORDER BY / LIMIT condition(s); prior to
// MediaWiki 1.26 this used to use the plus operator instead, which meant
// that extensions weren't able to change these conditions
- $query_options = array_merge( [
- 'ORDER BY' => 'rc_timestamp DESC',
- 'LIMIT' => $opts['limit'] ], $query_options );
+ $query_options = array_merge( $orderByAndLimit, $query_options );
$rows = $dbr->select(
$tables,
$fields,
[ 'class' => 'rcfilters-container' ]
);
+ $loadingContainer = Html::rawElement(
+ 'div',
+ [ 'class' => 'rcfilters-spinner' ],
+ Html::element(
+ 'div',
+ [ 'class' => 'rcfilters-spinner-bounce' ]
+ )
+ );
+
// Wrap both with rcfilters-head
$this->getOutput()->addHTML(
Html::rawElement(
$rcfilterContainer . $rcoptions
)
);
+
+ // Add spinner
+ $this->getOutput()->addHTML( $loadingContainer );
} else {
$this->getOutput()->addHTML( $rcoptions );
}
$message = $this->msg( 'recentchangestext' )->inContentLanguage();
if ( !$message->isDisabled() ) {
- $this->getOutput()->addWikiText(
- Html::rawElement( 'div',
- [
- 'class' => 'mw-recentchanges-toplinks',
- 'lang' => $wgContLang->getHtmlCode(),
- 'dir' => $wgContLang->getDir()
- ],
- "\n" . $message->plain() . "\n"
- ),
- /* $lineStart */ true,
- /* $interface */ false
+ $content = $message->parse();
+
+ $langAttributes = [
+ 'lang' => $wgContLang->getHtmlCode(),
+ 'dir' => $wgContLang->getDir(),
+ ];
+
+ $topLinksAttributes = [ 'class' => 'mw-recentchanges-toplinks' ];
+
+ if ( $this->getUser()->getOption( 'rcenhancedfilters' ) ) {
+ $contentTitle = Html::rawElement( 'div',
+ [ 'class' => 'mw-recentchanges-toplinks-title' ],
+ $this->msg( 'rcfilters-other-review-tools' )->parse()
+ );
+ $contentWrapper = Html::rawElement( 'div',
+ array_merge( [ 'class' => 'mw-collapsible-content' ], $langAttributes ),
+ $content
+ );
+ $content = $contentTitle . $contentWrapper;
+ } else {
+ // Language direction should be on the top div only
+ // if the title is not there. If it is there, it's
+ // interface direction, and the language/dir attributes
+ // should be on the content itself
+ $topLinksAttributes = array_merge( $topLinksAttributes, $langAttributes );
+ }
+
+ $this->getOutput()->addHTML(
+ Html::rawElement( 'div', $topLinksAttributes, $content )
);
}
}
$resetLink = $this->makeOptionsLink( $this->msg( 'rclistfromreset' ),
[ 'from' => '' ], $nondefaults );
- $note .= $this->msg( 'rcnotefrom' )
+ $noteFromMsg = $this->msg( 'rcnotefrom' )
->numParams( $options['limit'] )
->params(
$lang->userTimeAndDate( $options['from'], $user ),
$lang->userDate( $options['from'], $user ),
$lang->userTime( $options['from'], $user )
)
- ->numParams( $numRows )
- ->parse() . ' ' .
+ ->numParams( $numRows );
+ $note .= Html::rawElement(
+ 'span',
+ [ 'class' => 'rcnotefrom' ],
+ $noteFromMsg->parse()
+ ) .
+ ' ' .
Html::rawElement(
'span',
[ 'class' => 'rcoptions-listfromreset' ],