public function execute( $subpage ) {
$this->rcSubpage = $subpage;
+ $this->considerActionsForDefaultSavedQuery();
+
$rows = $this->getRows();
$opts = $this->getOptions();
if ( $rows === false ) {
$this->includeRcFiltersApp();
}
+ /**
+ * Check whether or not the page should load defaults, and if so, whether
+ * a default saved query is relevant to be redirected to. If it is relevant,
+ * redirect properly with all necessary query parameters.
+ */
+ protected function considerActionsForDefaultSavedQuery() {
+ if ( !$this->isStructuredFilterUiEnabled() ) {
+ return;
+ }
+
+ $knownParams = call_user_func_array(
+ [ $this->getRequest(), 'getValues' ],
+ array_keys( $this->getOptions()->getAllValues() )
+ );
+
+ // HACK: Temporarily until we can properly define "sticky" filters and parameters,
+ // we need to exclude several parameters we know should not be counted towards preventing
+ // the loading of defaults.
+ $excludedParams = [ 'limit' => '', 'days' => '', 'enhanced' => '', 'from' => '' ];
+ $knownParams = array_diff_key( $knownParams, $excludedParams );
+
+ if (
+ // If there are NO known parameters in the URL request
+ // (that are not excluded) then we need to check into loading
+ // the default saved query
+ count( $knownParams ) === 0
+ ) {
+ // Get the saved queries data and parse it
+ $savedQueries = FormatJson::decode(
+ $this->getUser()->getOption( static::$savedQueriesPreferenceName ),
+ true
+ );
+
+ if ( $savedQueries && isset( $savedQueries[ 'default' ] ) ) {
+ // Only load queries that are 'version' 2, since those
+ // have parameter representation
+ if ( isset( $savedQueries[ 'version' ] ) && $savedQueries[ 'version' ] === '2' ) {
+ $savedQueryDefaultID = $savedQueries[ 'default' ];
+ $defaultQuery = $savedQueries[ 'queries' ][ $savedQueryDefaultID ][ 'data' ];
+
+ // Build the entire parameter list
+ $query = array_merge(
+ $defaultQuery[ 'params' ],
+ $defaultQuery[ 'highlights' ],
+ [
+ 'urlversion' => '2',
+ ]
+ );
+ // Add to the query any parameters that we may have ignored before
+ // but are still valid and requested in the URL
+ $query = array_merge( $this->getRequest()->getValues(), $query );
+ unset( $query[ 'title' ] );
+ $this->getOutput()->redirect( $this->getPageTitle()->getCanonicalURL( $query ) );
+ } else {
+ // There's a default, but the version is not 2, and the server can't
+ // actually recognize the query itself. This happens if it is before
+ // the conversion, so we need to tell the UI to reload saved query as
+ // it does the conversion to version 2
+ $this->getOutput()->addJsConfigVars(
+ 'wgStructuredChangeFiltersDefaultSavedQueryExists',
+ true
+ );
+
+ // Add the class that tells the frontend it is still loading
+ // another query
+ $this->getOutput()->addBodyClasses( 'mw-rcfilters-ui-loading' );
+ }
+ }
+ }
+ }
+
/**
* Include the modules and configuration for the RCFilters app.
* Conditional on the user having the feature enabled.
$out->addJsConfigVars(
'wgRCFiltersChangeTags',
- $this->buildChangeTagList()
+ $this->getChangeTagList()
);
$out->addJsConfigVars(
'StructuredChangeFiltersDisplayConfig',
]
);
+ $out->addJsConfigVars(
+ 'wgStructuredChangeFiltersSavedQueriesPreferenceName',
+ static::$savedQueriesPreferenceName
+ );
+
$out->addJsConfigVars(
'StructuredChangeFiltersLiveUpdatePollingRate',
$this->getConfig()->get( 'StructuredChangeFiltersLiveUpdatePollingRate' )
);
-
- if ( static::$savedQueriesPreferenceName ) {
- $savedQueries = FormatJson::decode(
- $this->getUser()->getOption( static::$savedQueriesPreferenceName )
- );
- if ( $savedQueries && isset( $savedQueries->default ) ) {
- // If there is a default saved query, show a loading spinner,
- // since the frontend is going to reload the results
- $out->addBodyClasses( 'mw-rcfilters-ui-loading' );
- }
- $out->addJsConfigVars(
- 'wgStructuredChangeFiltersSavedQueriesPreferenceName',
- static::$savedQueriesPreferenceName
- );
- }
} else {
$out->addBodyClasses( 'mw-rcfilters-disabled' );
}
*
* @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,
- ];
- }
- }
+ protected function getChangeTagList() {
+ $cache = ObjectCache::getMainWANInstance();
+ $context = $this->getContext();
+ return $cache->getWithSetCallback(
+ $cache->makeKey( 'changeslistspecialpage-changetags', $context->getLanguage()->getCode() ),
+ $cache::TTL_MINUTE * 10,
+ function () use ( $context ) {
+ $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, $context );
+
+ $result[] = [
+ 'name' => $tagName,
+ 'label' => Sanitizer::stripAllTags(
+ ChangeTags::tagDescription( $tagName, $context )
+ ),
+ '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'] );
- } );
+ // 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;
+ return $result;
+ },
+ [
+ 'lockTSE' => 30
+ ]
+ );
}
/**
$opts->add( 'urlversion', 1 );
$opts->add( 'tagfilter', '' );
+ $opts->add( 'days', $this->getDefaultDays(), FormOptions::FLOAT );
+ $opts->add( 'limit', $this->getDefaultLimit(), FormOptions::INT );
+
+ $opts->add( 'from', '' );
+
return $opts;
}
$query = wfArrayToCgi( $this->convertParamsForLink( $opts->getChangedValues() ) );
$this->getOutput()->redirect( $this->getPageTitle()->getCanonicalURL( $query ) );
}
+
+ $opts->validateIntBounds( 'limit', 0, 5000 );
+ $opts->validateBounds( 'days', 0, $this->getConfig()->get( 'RCMaxAge' ) / ( 3600 * 24 ) );
}
/**
}
$conds[] = "rc_namespace $operator $value";
}
+
+ // Calculate cutoff
+ $cutoff_unixtime = time() - $opts['days'] * 3600 * 24;
+ $cutoff = $dbr->timestamp( $cutoff_unixtime );
+
+ $fromValid = preg_match( '/^[0-9]{14}$/', $opts['from'] );
+ if ( $fromValid && $opts['from'] > wfTimestamp( TS_MW, $cutoff ) ) {
+ $cutoff = $dbr->timestamp( $opts['from'] );
+ } else {
+ $opts->reset( 'from' );
+ }
+
+ $conds[] = 'rc_timestamp >= ' . $dbr->addQuotes( $cutoff );
}
/**
$context->msg( 'recentchanges-legend-heading' )->parse();
# Collapsible
+ $collapsedState = $this->getRequest()->getCookie( 'changeslist-state' );
+ $collapsedClass = $collapsedState === 'collapsed' ? ' mw-collapsed' : '';
$legend =
- '<div class="mw-changeslist-legend">' .
+ '<div class="mw-changeslist-legend mw-collapsible' . $collapsedClass . '">' .
$legendHeading .
'<div class="mw-collapsible-content">' . $legend . '</div>' .
'</div>';