public function execute( $subpage ) {
$this->rcSubpage = $subpage;
- $this->setHeaders();
- $this->outputHeader();
- $this->addModules();
-
$rows = $this->getRows();
$opts = $this->getOptions();
if ( $rows === false ) {
$rows = new FakeResultWrapper( [] );
}
+ // Used by Structured UI app to get results without MW chrome
+ if ( $this->getRequest()->getVal( 'action' ) === 'render' ) {
+ $this->getOutput()->setArticleBodyOnly( true );
+ }
+
+ // Used by "live update" and "view newest" to check
+ // if there's new changes with minimal data transfer
+ if ( $this->getRequest()->getBool( 'peek' ) ) {
+ $code = $rows->numRows() > 0 ? 200 : 204;
+ $this->getOutput()->setStatusCode( $code );
+ return;
+ }
+
$batch = new LinkBatch;
foreach ( $rows as $row ) {
$batch->add( NS_USER, $row->rc_user_text );
}
}
$batch->execute();
+
+ $this->setHeaders();
+ $this->outputHeader();
+ $this->addModules();
$this->webOutput( $rows, $opts );
$rows->free();
LoggerFactory::getInstance( 'objectcache' )
) );
}
+
+ $this->includeRcFiltersApp();
+ }
+
+ /**
+ * Include the modules and configuration for the RCFilters app.
+ * Conditional on the user having the feature enabled.
+ *
+ * If it is disabled, add a <body> class marking that
+ */
+ protected function includeRcFiltersApp() {
+ $out = $this->getOutput();
+ if ( $this->isStructuredFilterUiEnabled() ) {
+ $jsData = $this->getStructuredFilterJsData();
+
+ $messages = [];
+ foreach ( $jsData['messageKeys'] as $key ) {
+ $messages[$key] = $this->msg( $key )->plain();
+ }
+
+ $out->addBodyClasses( 'mw-rcfilters-enabled' );
+
+ $out->addHTML(
+ ResourceLoader::makeInlineScript(
+ ResourceLoader::makeMessageSetScript( $messages )
+ )
+ );
+
+ $experimentalStructuredChangeFilters =
+ $this->getConfig()->get( 'StructuredChangeFiltersEnableExperimentalViews' );
+
+ $out->addJsConfigVars( 'wgStructuredChangeFilters', $jsData['groups'] );
+ $out->addJsConfigVars(
+ 'wgStructuredChangeFiltersEnableExperimentalViews',
+ $experimentalStructuredChangeFilters
+ );
+ $out->addJsConfigVars(
+ 'wgRCFiltersChangeTags',
+ $this->buildChangeTagList()
+ );
+ $out->addJsConfigVars(
+ 'StructuredChangeFiltersDisplayConfig',
+ [
+ 'maxDays' => (int)$this->getConfig()->get( 'RCMaxAge' ) / ( 24 * 3600 ), // Translate to days
+ 'limitArray' => $this->getConfig()->get( 'RCLinkLimits' ),
+ 'limitDefault' => $this->getDefaultLimit(),
+ 'daysArray' => $this->getConfig()->get( 'RCLinkDays' ),
+ 'daysDefault' => $this->getDefaultDays(),
+ ]
+ );
+ } else {
+ $out->addBodyClasses( 'mw-rcfilters-disabled' );
+ }
+ }
+
+ /**
+ * Fetch the change tags list for the front end
+ *
+ * @return Array Tag data
+ */
+ protected function buildChangeTagList() {
+ $explicitlyDefinedTags = array_fill_keys( ChangeTags::listExplicitlyDefinedTags(), 0 );
+ $softwareActivatedTags = array_fill_keys( ChangeTags::listSoftwareActivatedTags(), 0 );
+
+ // Hit counts disabled for perf reasons, see T169997
+ /*
+ $tagStats = ChangeTags::tagUsageStatistics();
+ $tagHitCounts = array_merge( $explicitlyDefinedTags, $softwareActivatedTags, $tagStats );
+
+ // Sort by hits
+ arsort( $tagHitCounts );
+ */
+ $tagHitCounts = array_merge( $explicitlyDefinedTags, $softwareActivatedTags );
+
+ // Build the list and data
+ $result = [];
+ foreach ( $tagHitCounts as $tagName => $hits ) {
+ if (
+ // Only get active tags
+ isset( $explicitlyDefinedTags[ $tagName ] ) ||
+ isset( $softwareActivatedTags[ $tagName ] )
+ ) {
+ // Parse description
+ $desc = ChangeTags::tagLongDescriptionMessage( $tagName, $this->getContext() );
+
+ $result[] = [
+ 'name' => $tagName,
+ 'label' => Sanitizer::stripAllTags(
+ ChangeTags::tagDescription( $tagName, $this->getContext() )
+ ),
+ 'description' => $desc ? Sanitizer::stripAllTags( $desc->parse() ) : '',
+ 'cssClass' => Sanitizer::escapeClass( 'mw-tag-' . $tagName ),
+ 'hits' => $hits,
+ ];
+ }
+ }
+
+ // Instead of sorting by hit count (disabled, see above), sort by display name
+ usort( $result, function ( $a, $b ) {
+ return strcasecmp( $a['label'], $b['label'] );
+ } );
+
+ return $result;
}
/**
* ChangesListFilterGroup constructors.
*
* There is light processing to simplify core maintenance.
+ * @param array $definition
*/
protected function registerFiltersFromDefinitions( array $definition ) {
$autoFillPriority = -1;
* @return FormOptions
*/
public function getDefaultOptions() {
- $config = $this->getConfig();
$opts = new FormOptions();
- $structuredUI = $this->getUser()->getOption( 'rcenhancedfilters' );
+ $structuredUI = $this->isStructuredFilterUiEnabled();
// If urlversion=2 is set, ignore the filter defaults and set them all to false/empty
$useDefaults = $this->getRequest()->getInt( 'urlversion' ) !== 2;
// Add all filters
+ /** @var ChangesListFilterGroup $filterGroup */
foreach ( $this->filterGroups as $filterGroup ) {
// URL parameters can be per-group, like 'userExpLevel',
// or per-filter, like 'hideminor'.
if ( $filterGroup->isPerGroupRequestParameter() ) {
$opts->add( $filterGroup->getName(), $useDefaults ? $filterGroup->getDefault() : '' );
} else {
+ /** @var ChangesListBooleanFilter $filter */
foreach ( $filterGroup->getFilters() as $filter ) {
$opts->add( $filter->getName(), $useDefaults ? $filter->getDefault( $structuredUI ) : false );
}
$opts->add( 'invert', false );
$opts->add( 'associated', false );
$opts->add( 'urlversion', 1 );
+ $opts->add( 'tagfilter', '' );
return $opts;
}
'messageKeys' => [],
];
- $context = $this->getContext();
-
usort( $this->filterGroups, function ( $a, $b ) {
return $b->getPriority() - $a->getPriority();
} );
&$join_conds, FormOptions $opts
) {
$dbr = $this->getDB();
- $user = $this->getUser();
+ $isStructuredUI = $this->isStructuredFilterUiEnabled();
- $context = $this->getContext();
foreach ( $this->filterGroups as $filterGroup ) {
// URL parameters can be per-group, like 'userExpLevel',
// or per-filter, like 'hideminor'.
$query_options, $join_conds, $opts[$filterGroup->getName()] );
} else {
foreach ( $filterGroup->getFilters() as $filter ) {
- if ( $opts[$filter->getName()] ) {
+ if ( $filter->isActive( $opts, $isStructuredUI ) ) {
$filter->modifyQuery( $dbr, $this, $tables, $fields, $conds,
$query_options, $join_conds );
}
) . "\n";
$legend .= Html::closeElement( 'dl' ) . "\n";
- # Collapsibility
- $legendHeading = $this->getUser()->getOption(
- 'rcenhancedfilters'
- ) ?
+ $legendHeading = $this->isStructuredFilterUiEnabled() ?
$context->msg( 'rcfilters-legend-heading' )->parse() :
$context->msg( 'recentchanges-legend-heading' )->parse();
+
+ # Collapsible
$legend =
'<div class="mw-changeslist-legend">' .
$legendHeading .
'mediawiki.special.changeslist',
] );
$out->addModules( 'mediawiki.special.changeslist.legend.js' );
+
+ if ( $this->isStructuredFilterUiEnabled() ) {
+ $out->addModules( 'mediawiki.rcfilters.filters.ui' );
+ $out->addModuleStyles( 'mediawiki.rcfilters.filters.base.styles' );
+ }
}
protected function getGroupName() {
$conds[] = reset( $conditions );
}
}
+
+ /**
+ * Check whether the structured filter UI is enabled
+ *
+ * @return bool
+ */
+ public function isStructuredFilterUiEnabled() {
+ if ( $this->getRequest()->getBool( 'rcfilters' ) ) {
+ return true;
+ }
+
+ if ( $this->getConfig()->get( 'StructuredChangeFiltersShowPreference' ) ) {
+ return !$this->getUser()->getOption( 'rcenhancedfilters-disable' );
+ } else {
+ return $this->getUser()->getOption( 'rcenhancedfilters' );
+ }
+ }
+
+ /**
+ * Check whether the structured filter UI is enabled by default (regardless of
+ * this particular user's setting)
+ *
+ * @return bool
+ */
+ public function isStructuredFilterUiEnabledByDefault() {
+ if ( $this->getConfig()->get( 'StructuredChangeFiltersShowPreference' ) ) {
+ return !$this->getUser()->getDefaultOption( 'rcenhancedfilters-disable' );
+ } else {
+ return $this->getUser()->getDefaultOption( 'rcenhancedfilters' );
+ }
+ }
+
+ abstract function getDefaultLimit();
+
+ abstract function getDefaultDays();
}