parent::execute( $subpage );
if ( $this->isStructuredFilterUiEnabled() ) {
+ $output->addModuleStyles( [ 'mediawiki.rcfilters.highlightCircles.seenunseen.styles' ] );
+
$output->addJsConfigVars( 'wgStructuredChangeFiltersLiveUpdateSupported', false );
$output->addJsConfigVars(
'wgStructuredChangeFiltersSavedQueriesPreferenceName',
'rcfilters-wl-saved-queries'
);
+ $output->addJsConfigVars(
+ 'wgStructuredChangeFiltersEditWatchlistUrl',
+ SpecialPage::getTitleFor( 'EditWatchlist' )->getLocalURL()
+ );
}
}
- protected function isStructuredFilterUiEnabled() {
+ public function isStructuredFilterUiEnabled() {
return parent::isStructuredFilterUiEnabled()
&& ( $this->getConfig()->get( 'StructuredChangeFiltersOnWatchlist' )
|| $this->getRequest()->getBool( 'rcfilters' ) );
protected function registerFilters() {
parent::registerFilters();
+ // legacy 'extended' filter
+ $this->registerFilterGroup( new ChangesListBooleanFilterGroup( [
+ 'name' => 'extended-group',
+ 'filters' => [
+ [
+ 'name' => 'extended',
+ 'isReplacedInStructuredUi' => true,
+ 'activeValue' => false,
+ 'default' => $this->getUser()->getBoolOption( 'extendwatchlist' ),
+ 'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables,
+ &$fields, &$conds, &$query_options, &$join_conds ) {
+ $nonRevisionTypes = [ RC_LOG ];
+ Hooks::run( 'SpecialWatchlistGetNonRevisionTypes', [ &$nonRevisionTypes ] );
+ if ( $nonRevisionTypes ) {
+ $conds[] = $dbr->makeList(
+ [
+ 'rc_this_oldid=page_latest',
+ 'rc_type' => $nonRevisionTypes,
+ ],
+ LIST_OR
+ );
+ }
+ },
+ ]
+ ],
+
+ ] ) );
+
+ if ( $this->isStructuredFilterUiEnabled() ) {
+ $this->getFilterGroup( 'lastRevision' )
+ ->getFilter( 'hidepreviousrevisions' )
+ ->setDefault( !$this->getUser()->getBoolOption( 'extendwatchlist' ) );
+ }
+
$this->registerFilterGroup( new ChangesListStringOptionsFilterGroup( [
'name' => 'watchlistactivity',
'title' => 'rcfilters-filtergroup-watchlistactivity',
'isRowApplicableCallable' => function ( $ctx, $rc ) {
$changeTs = $rc->getAttribute( 'rc_timestamp' );
$lastVisitTs = $rc->getAttribute( 'wl_notificationtimestamp' );
- return $changeTs >= $lastVisitTs;
+ return $lastVisitTs !== null && $changeTs >= $lastVisitTs;
},
],
[
'isRowApplicableCallable' => function ( $ctx, $rc ) {
$changeTs = $rc->getAttribute( 'rc_timestamp' );
$lastVisitTs = $rc->getAttribute( 'wl_notificationtimestamp' );
- return $changeTs < $lastVisitTs;
+ return $lastVisitTs === null || $changeTs < $lastVisitTs;
}
],
],
'queryCallable' => function ( $specialPageClassName, $context, $dbr,
&$tables, &$fields, &$conds, &$query_options, &$join_conds, $selectedValues ) {
if ( $selectedValues === [ 'seen' ] ) {
- $conds[] = 'rc_timestamp < wl_notificationtimestamp';
+ $conds[] = $dbr->makeList( [
+ 'wl_notificationtimestamp IS NULL',
+ 'rc_timestamp < wl_notificationtimestamp'
+ ], LIST_OR );
} elseif ( $selectedValues === [ 'unseen' ] ) {
- $conds[] = 'rc_timestamp >= wl_notificationtimestamp';
+ $conds[] = $dbr->makeList( [
+ 'wl_notificationtimestamp IS NOT NULL',
+ 'rc_timestamp >= wl_notificationtimestamp'
+ ], LIST_AND );
}
}
] ) );
$user = $this->getUser();
$opts->add( 'days', $user->getOption( 'watchlistdays' ), FormOptions::FLOAT );
- $opts->add( 'extended', $user->getBoolOption( 'extendwatchlist' ) );
$opts->add( 'limit', $user->getIntOption( 'wllimit' ), FormOptions::INT );
return $opts;
// unchecked boxes.
foreach ( $this->filterGroups as $filterGroup ) {
if ( $filterGroup instanceof ChangesListBooleanFilterGroup ) {
+ /** @var ChangesListBooleanFilter $filter */
foreach ( $filterGroup->getFilters() as $filter ) {
- $allBooleansFalse[$filter->getName()] = false;
+ if ( $filter->displaysOnUnstructuredUi() ) {
+ $allBooleansFalse[$filter->getName()] = false;
+ }
}
}
}
$dbr = $this->getDB();
$user = $this->getUser();
- # Toggle watchlist content (all recent edits or just the latest)
- if ( $opts['extended'] ) {
- $usePage = false;
- } else {
- # Top log Ids for a page are not stored
- $nonRevisionTypes = [ RC_LOG ];
- Hooks::run( 'SpecialWatchlistGetNonRevisionTypes', [ &$nonRevisionTypes ] );
- if ( $nonRevisionTypes ) {
- $conds[] = $dbr->makeList(
- [
- 'rc_this_oldid=page_latest',
- 'rc_type' => $nonRevisionTypes,
- ],
- LIST_OR
- );
- }
- $usePage = true;
- }
-
$tables = array_merge( [ 'recentchanges', 'watchlist' ], $tables );
$fields = array_merge( RecentChange::selectFields(), $fields );
- $query_options = array_merge( [
- 'ORDER BY' => 'rc_timestamp DESC',
- 'LIMIT' => $opts['limit']
- ], $query_options );
$join_conds = array_merge(
[
'watchlist' => [
$join_conds
);
- if ( $this->getConfig()->get( 'ShowUpdatedMarker' ) ) {
- $fields[] = 'wl_notificationtimestamp';
- }
+ $tables[] = 'page';
+ $fields[] = 'page_latest';
+ $join_conds['page'] = [ 'LEFT JOIN', 'rc_cur_id=page_id' ];
- $rollbacker = $user->isAllowed( 'rollback' );
- if ( $usePage || $rollbacker ) {
- $tables[] = 'page';
- $join_conds['page'] = [ 'LEFT JOIN', 'rc_cur_id=page_id' ];
- if ( $rollbacker ) {
- $fields[] = 'page_latest';
- }
- }
+ $fields[] = 'wl_notificationtimestamp';
// Log entries with DELETED_ACTION must not show up unless the user has
// the necessary rights.
], LIST_OR );
}
+ $tagFilter = $opts['tagfilter'] ? explode( '|', $opts['tagfilter'] ) : [];
ChangeTags::modifyDisplayQuery(
$tables,
$fields,
$conds,
$join_conds,
$query_options,
- ''
+ $tagFilter
);
$this->runMainQueryHook( $tables, $fields, $conds, $query_options, $join_conds, $opts );
+ if ( $this->areFiltersInConflict() ) {
+ 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)
+ $query_options = array_merge( $orderByAndLimit, $query_options );
+
return $dbr->select(
$tables,
$fields,
$showUpdatedMarker = $this->getConfig()->get( 'ShowUpdatedMarker' );
// Show watchlist header
- $form .= "<p>";
+ $watchlistHeader = '';
if ( $numItems == 0 ) {
- $form .= $this->msg( 'nowatchlist' )->parse() . "\n";
+ $watchlistHeader = $this->msg( 'nowatchlist' )->parse();
} else {
- $form .= $this->msg( 'watchlist-details' )->numParams( $numItems )->parse() . "\n";
+ $watchlistHeader .= $this->msg( 'watchlist-details' )->numParams( $numItems )->parse() . "\n";
if ( $this->getConfig()->get( 'EnotifWatchlist' )
&& $user->getOption( 'enotifwatchlistpages' )
) {
- $form .= $this->msg( 'wlheader-enotif' )->parse() . "\n";
+ $watchlistHeader .= $this->msg( 'wlheader-enotif' )->parse() . "\n";
}
if ( $showUpdatedMarker ) {
- $form .= $this->msg( 'wlheader-showupdated' )->parse() . "\n";
+ $watchlistHeader .= $this->msg(
+ $this->isStructuredFilterUiEnabled() ?
+ 'rcfilters-watchlist-showupdated' :
+ 'wlheader-showupdated'
+ )->parse() . "\n";
}
}
- $form .= "</p>";
+ $form .= Html::rawElement(
+ 'div',
+ [ 'class' => 'watchlistDetails' ],
+ $watchlistHeader
+ );
if ( $numItems > 0 && $showUpdatedMarker ) {
$form .= Xml::openElement( 'form', [ 'method' => 'post',
$count = $store->countWatchedItems( $this->getUser() );
return floor( $count / 2 );
}
+
+ function getDefaultLimit() {
+ return $this->getUser()->getIntOption( 'wllimit' );
+ }
+
+ function getDefaultDays() {
+ return $this->getUser()->getIntOption( 'watchlistdays' );
+ }
}