107 parent::__construct(
$name, $restriction );
109 $nonRevisionTypes = [
RC_LOG ];
110 Hooks::run(
'SpecialWatchlistGetNonRevisionTypes', [ &$nonRevisionTypes ] );
112 $this->filterGroupDefinitions = [
114 'name' =>
'registration',
115 'title' =>
'rcfilters-filtergroup-registration',
122 'showHideSuffix' =>
'showhideliu',
124 'queryCallable' =>
function ( $specialClassName, $ctx,
$dbr, &
$tables, &$fields, &$conds,
125 &$query_options, &$join_conds
128 $actorQuery = $actorMigration->getJoin(
'rc_user' );
129 $tables += $actorQuery[
'tables'];
130 $join_conds += $actorQuery[
'joins'];
131 $conds[] = $actorMigration->isAnon( $actorQuery[
'fields'][
'rc_user'] );
133 'isReplacedInStructuredUi' =>
true,
137 'name' =>
'hideanons',
140 'showHideSuffix' =>
'showhideanons',
142 'queryCallable' =>
function ( $specialClassName, $ctx,
$dbr, &
$tables, &$fields, &$conds,
143 &$query_options, &$join_conds
146 $actorQuery = $actorMigration->getJoin(
'rc_user' );
147 $tables += $actorQuery[
'tables'];
148 $join_conds += $actorQuery[
'joins'];
149 $conds[] = $actorMigration->isNotAnon( $actorQuery[
'fields'][
'rc_user'] );
151 'isReplacedInStructuredUi' =>
true,
157 'name' =>
'userExpLevel',
158 'title' =>
'rcfilters-filtergroup-userExpLevel',
160 'isFullCoverage' =>
true,
163 'name' =>
'unregistered',
164 'label' =>
'rcfilters-filter-user-experience-level-unregistered-label',
165 'description' =>
'rcfilters-filter-user-experience-level-unregistered-description',
166 'cssClassSuffix' =>
'user-unregistered',
167 'isRowApplicableCallable' =>
function ( $ctx, $rc ) {
168 return !$rc->getAttribute(
'rc_user' );
172 'name' =>
'registered',
173 'label' =>
'rcfilters-filter-user-experience-level-registered-label',
174 'description' =>
'rcfilters-filter-user-experience-level-registered-description',
175 'cssClassSuffix' =>
'user-registered',
176 'isRowApplicableCallable' =>
function ( $ctx, $rc ) {
177 return $rc->getAttribute(
'rc_user' );
181 'name' =>
'newcomer',
182 'label' =>
'rcfilters-filter-user-experience-level-newcomer-label',
183 'description' =>
'rcfilters-filter-user-experience-level-newcomer-description',
184 'cssClassSuffix' =>
'user-newcomer',
185 'isRowApplicableCallable' =>
function ( $ctx, $rc ) {
186 $performer = $rc->getPerformer();
187 return $performer && $performer->isLoggedIn() &&
188 $performer->getExperienceLevel() ===
'newcomer';
193 'label' =>
'rcfilters-filter-user-experience-level-learner-label',
194 'description' =>
'rcfilters-filter-user-experience-level-learner-description',
195 'cssClassSuffix' =>
'user-learner',
196 'isRowApplicableCallable' =>
function ( $ctx, $rc ) {
197 $performer = $rc->getPerformer();
198 return $performer && $performer->isLoggedIn() &&
199 $performer->getExperienceLevel() ===
'learner';
203 'name' =>
'experienced',
204 'label' =>
'rcfilters-filter-user-experience-level-experienced-label',
205 'description' =>
'rcfilters-filter-user-experience-level-experienced-description',
206 'cssClassSuffix' =>
'user-experienced',
207 'isRowApplicableCallable' =>
function ( $ctx, $rc ) {
208 $performer = $rc->getPerformer();
209 return $performer && $performer->isLoggedIn() &&
210 $performer->getExperienceLevel() ===
'experienced';
215 'queryCallable' => [ $this,
'filterOnUserExperienceLevel' ],
219 'name' =>
'authorship',
220 'title' =>
'rcfilters-filtergroup-authorship',
224 'name' =>
'hidemyself',
225 'label' =>
'rcfilters-filter-editsbyself-label',
226 'description' =>
'rcfilters-filter-editsbyself-description',
229 'showHideSuffix' =>
'showhidemine',
231 'queryCallable' =>
function ( $specialClassName, $ctx,
$dbr, &
$tables, &$fields, &$conds,
232 &$query_options, &$join_conds
235 $tables += $actorQuery[
'tables'];
236 $join_conds += $actorQuery[
'joins'];
237 $conds[] =
'NOT(' . $actorQuery[
'conds'] .
')';
239 'cssClassSuffix' =>
'self',
240 'isRowApplicableCallable' =>
function ( $ctx, $rc ) {
241 return $ctx->getUser()->equals( $rc->getPerformer() );
245 'name' =>
'hidebyothers',
246 'label' =>
'rcfilters-filter-editsbyother-label',
247 'description' =>
'rcfilters-filter-editsbyother-description',
249 'queryCallable' =>
function ( $specialClassName, $ctx,
$dbr, &
$tables, &$fields, &$conds,
250 &$query_options, &$join_conds
253 ->getWhere(
$dbr,
'rc_user', $ctx->getUser(),
false );
254 $tables += $actorQuery[
'tables'];
255 $join_conds += $actorQuery[
'joins'];
256 $conds[] = $actorQuery[
'conds'];
258 'cssClassSuffix' =>
'others',
259 'isRowApplicableCallable' =>
function ( $ctx, $rc ) {
260 return !$ctx->getUser()->equals( $rc->getPerformer() );
267 'name' =>
'automated',
268 'title' =>
'rcfilters-filtergroup-automated',
272 'name' =>
'hidebots',
273 'label' =>
'rcfilters-filter-bots-label',
274 'description' =>
'rcfilters-filter-bots-description',
277 'showHideSuffix' =>
'showhidebots',
279 'queryCallable' =>
function ( $specialClassName, $ctx,
$dbr, &
$tables, &$fields, &$conds,
280 &$query_options, &$join_conds
282 $conds[
'rc_bot'] = 0;
284 'cssClassSuffix' =>
'bot',
285 'isRowApplicableCallable' =>
function ( $ctx, $rc ) {
286 return $rc->getAttribute(
'rc_bot' );
290 'name' =>
'hidehumans',
291 'label' =>
'rcfilters-filter-humans-label',
292 'description' =>
'rcfilters-filter-humans-description',
294 'queryCallable' =>
function ( $specialClassName, $ctx,
$dbr, &
$tables, &$fields, &$conds,
295 &$query_options, &$join_conds
297 $conds[
'rc_bot'] = 1;
299 'cssClassSuffix' =>
'human',
300 'isRowApplicableCallable' =>
function ( $ctx, $rc ) {
301 return !$rc->getAttribute(
'rc_bot' );
310 'name' =>
'significance',
311 'title' =>
'rcfilters-filtergroup-significance',
316 'name' =>
'hideminor',
317 'label' =>
'rcfilters-filter-minor-label',
318 'description' =>
'rcfilters-filter-minor-description',
321 'showHideSuffix' =>
'showhideminor',
323 'queryCallable' =>
function ( $specialClassName, $ctx,
$dbr, &
$tables, &$fields, &$conds,
324 &$query_options, &$join_conds
326 $conds[] =
'rc_minor = 0';
328 'cssClassSuffix' =>
'minor',
329 'isRowApplicableCallable' =>
function ( $ctx, $rc ) {
330 return $rc->getAttribute(
'rc_minor' );
334 'name' =>
'hidemajor',
335 'label' =>
'rcfilters-filter-major-label',
336 'description' =>
'rcfilters-filter-major-description',
338 'queryCallable' =>
function ( $specialClassName, $ctx,
$dbr, &
$tables, &$fields, &$conds,
339 &$query_options, &$join_conds
341 $conds[] =
'rc_minor = 1';
343 'cssClassSuffix' =>
'major',
344 'isRowApplicableCallable' =>
function ( $ctx, $rc ) {
345 return !$rc->getAttribute(
'rc_minor' );
352 'name' =>
'lastRevision',
353 'title' =>
'rcfilters-filtergroup-lastRevision',
358 'name' =>
'hidelastrevision',
359 'label' =>
'rcfilters-filter-lastrevision-label',
360 'description' =>
'rcfilters-filter-lastrevision-description',
362 'queryCallable' =>
function ( $specialClassName, $ctx,
$dbr, &
$tables, &$fields, &$conds,
363 &$query_options, &$join_conds )
use ( $nonRevisionTypes ) {
364 $conds[] =
$dbr->makeList(
366 'rc_this_oldid <> page_latest',
367 'rc_type' => $nonRevisionTypes,
372 'cssClassSuffix' =>
'last',
373 'isRowApplicableCallable' =>
function ( $ctx, $rc ) {
374 return $rc->getAttribute(
'rc_this_oldid' ) === $rc->getAttribute(
'page_latest' );
378 'name' =>
'hidepreviousrevisions',
379 'label' =>
'rcfilters-filter-previousrevision-label',
380 'description' =>
'rcfilters-filter-previousrevision-description',
382 'queryCallable' =>
function ( $specialClassName, $ctx,
$dbr, &
$tables, &$fields, &$conds,
383 &$query_options, &$join_conds )
use ( $nonRevisionTypes ) {
384 $conds[] =
$dbr->makeList(
386 'rc_this_oldid = page_latest',
387 'rc_type' => $nonRevisionTypes,
392 'cssClassSuffix' =>
'previous',
393 'isRowApplicableCallable' =>
function ( $ctx, $rc ) {
394 return $rc->getAttribute(
'rc_this_oldid' ) !== $rc->getAttribute(
'page_latest' );
402 'name' =>
'changeType',
403 'title' =>
'rcfilters-filtergroup-changetype',
408 'name' =>
'hidepageedits',
409 'label' =>
'rcfilters-filter-pageedits-label',
410 'description' =>
'rcfilters-filter-pageedits-description',
413 'queryCallable' =>
function ( $specialClassName, $ctx,
$dbr, &
$tables, &$fields, &$conds,
414 &$query_options, &$join_conds
416 $conds[] =
'rc_type != ' .
$dbr->addQuotes(
RC_EDIT );
418 'cssClassSuffix' =>
'src-mw-edit',
419 'isRowApplicableCallable' =>
function ( $ctx, $rc ) {
424 'name' =>
'hidenewpages',
425 'label' =>
'rcfilters-filter-newpages-label',
426 'description' =>
'rcfilters-filter-newpages-description',
429 'queryCallable' =>
function ( $specialClassName, $ctx,
$dbr, &
$tables, &$fields, &$conds,
430 &$query_options, &$join_conds
432 $conds[] =
'rc_type != ' .
$dbr->addQuotes(
RC_NEW );
434 'cssClassSuffix' =>
'src-mw-new',
435 'isRowApplicableCallable' =>
function ( $ctx, $rc ) {
444 'label' =>
'rcfilters-filter-logactions-label',
445 'description' =>
'rcfilters-filter-logactions-description',
448 'queryCallable' =>
function ( $specialClassName, $ctx,
$dbr, &
$tables, &$fields, &$conds,
449 &$query_options, &$join_conds
451 $conds[] =
'rc_type != ' .
$dbr->addQuotes(
RC_LOG );
453 'cssClassSuffix' =>
'src-mw-log',
454 'isRowApplicableCallable' =>
function ( $ctx, $rc ) {
463 $this->legacyReviewStatusFilterGroupDefinition = [
465 'name' =>
'legacyReviewStatus',
466 'title' =>
'rcfilters-filtergroup-reviewstatus',
470 'name' =>
'hidepatrolled',
473 'showHideSuffix' =>
'showhidepatr',
475 'queryCallable' =>
function ( $specialClassName, $ctx,
$dbr, &
$tables, &$fields, &$conds,
476 &$query_options, &$join_conds
480 'isReplacedInStructuredUi' =>
true,
483 'name' =>
'hideunpatrolled',
485 'queryCallable' =>
function ( $specialClassName, $ctx,
$dbr, &
$tables, &$fields, &$conds,
486 &$query_options, &$join_conds
490 'isReplacedInStructuredUi' =>
true,
496 $this->reviewStatusFilterGroupDefinition = [
498 'name' =>
'reviewStatus',
499 'title' =>
'rcfilters-filtergroup-reviewstatus',
501 'isFullCoverage' =>
true,
505 'name' =>
'unpatrolled',
506 'label' =>
'rcfilters-filter-reviewstatus-unpatrolled-label',
507 'description' =>
'rcfilters-filter-reviewstatus-unpatrolled-description',
508 'cssClassSuffix' =>
'reviewstatus-unpatrolled',
509 'isRowApplicableCallable' =>
function ( $ctx, $rc ) {
515 'label' =>
'rcfilters-filter-reviewstatus-manual-label',
516 'description' =>
'rcfilters-filter-reviewstatus-manual-description',
517 'cssClassSuffix' =>
'reviewstatus-manual',
518 'isRowApplicableCallable' =>
function ( $ctx, $rc ) {
524 'label' =>
'rcfilters-filter-reviewstatus-auto-label',
525 'description' =>
'rcfilters-filter-reviewstatus-auto-description',
526 'cssClassSuffix' =>
'reviewstatus-auto',
527 'isRowApplicableCallable' =>
function ( $ctx, $rc ) {
533 'queryCallable' =>
function ( $specialPageClassName, $ctx,
$dbr,
534 &
$tables, &$fields, &$conds, &$query_options, &$join_conds, $selected
536 if ( $selected === [] ) {
539 $rcPatrolledValues = [
545 $conds[
'rc_patrolled'] = array_map(
function (
$s )
use ( $rcPatrolledValues ) {
546 return $rcPatrolledValues[
$s ];
552 $this->hideCategorizationFilterDefinition = [
553 'name' =>
'hidecategorization',
554 'label' =>
'rcfilters-filter-categorization-label',
555 'description' =>
'rcfilters-filter-categorization-description',
558 'showHideSuffix' =>
'showhidecategorization',
561 'queryCallable' =>
function ( $specialClassName, $ctx,
$dbr, &
$tables, &$fields, &$conds,
562 &$query_options, &$join_conds
566 'cssClassSuffix' =>
'src-mw-categorize',
567 'isRowApplicableCallable' =>
function ( $ctx, $rc ) {
582 if ( $group->getConflictingGroups() ) {
585 " specifies conflicts with other groups but these are not supported yet."
590 foreach ( $group->getConflictingFilters()
as $conflictingFilter ) {
591 if ( $conflictingFilter->activelyInConflictWithGroup( $group, $opts ) ) {
597 foreach ( $group->getFilters()
as $filter ) {
599 foreach ( $filter->getConflictingFilters()
as $conflictingFilter ) {
601 $conflictingFilter->activelyInConflictWithFilter( $filter, $opts ) &&
602 $filter->activelyInConflictWithFilter( $conflictingFilter, $opts )
621 $this->rcSubpage = $subpage;
628 if (
$rows ===
false ) {
633 if ( $this->
getRequest()->getVal(
'action' ) ===
'render' ) {
634 $this->
getOutput()->setArticleBodyOnly(
true );
639 if ( $this->
getRequest()->getBool(
'peek' ) ) {
643 if ( $this->
getUser()->isAnon() !==
646 $this->
getOutput()->setStatusCode( 205 );
656 $batch->add( $row->rc_namespace, $row->rc_title );
659 foreach ( $formatter->getPreloadTitles()
as $title ) {
679 $this->
getOutput()->setStatusCode( 500 );
684 if ( $this->
getConfig()->
get(
'EnableWANCacheReaper' ) ) {
688 LoggerFactory::getInstance(
'objectcache' )
707 $knownParams = call_user_func_array(
709 array_keys( $this->
getOptions()->getAllValues() )
715 $excludedParams = [
'limit' =>
'',
'days' =>
'',
'enhanced' =>
'',
'from' =>
'' ];
716 $knownParams = array_diff_key( $knownParams, $excludedParams );
722 count( $knownParams ) === 0
726 $this->
getUser()->getOption( static::$savedQueriesPreferenceName ),
730 if ( $savedQueries && isset( $savedQueries[
'default' ] ) ) {
733 if ( isset( $savedQueries[
'version' ] ) && $savedQueries[
'version' ] ===
'2' ) {
734 $savedQueryDefaultID = $savedQueries[
'default' ];
735 $defaultQuery = $savedQueries[
'queries' ][ $savedQueryDefaultID ][
'data' ];
739 $defaultQuery[
'params' ],
740 $defaultQuery[
'highlights' ],
748 unset(
$query[
'title' ] );
756 'wgStructuredChangeFiltersDefaultSavedQueryExists',
762 $this->
getOutput()->addBodyClasses(
'mw-rcfilters-ui-loading' );
780 foreach ( $jsData[
'messageKeys']
as $key ) {
784 $out->addBodyClasses(
'mw-rcfilters-enabled' );
787 ResourceLoader::makeInlineScript(
788 ResourceLoader::makeMessageSetScript(
$messages )
792 $out->addJsConfigVars(
'wgStructuredChangeFilters', $jsData[
'groups'] );
794 $out->addJsConfigVars(
795 'wgRCFiltersChangeTags',
798 $out->addJsConfigVars(
799 'StructuredChangeFiltersDisplayConfig',
801 'maxDays' => (
int)$this->
getConfig()->
get(
'RCMaxAge' ) / ( 24 * 3600 ),
802 'limitArray' => $this->
getConfig()->
get(
'RCLinkLimits' ),
804 'daysArray' => $this->
getConfig()->
get(
'RCLinkDays' ),
809 $out->addJsConfigVars(
810 'wgStructuredChangeFiltersSavedQueriesPreferenceName',
811 static::$savedQueriesPreferenceName
813 $out->addJsConfigVars(
814 'wgStructuredChangeFiltersLimitPreferenceName',
815 static::$limitPreferenceName
817 $out->addJsConfigVars(
818 'wgStructuredChangeFiltersDaysPreferenceName',
819 static::$daysPreferenceName
822 $out->addJsConfigVars(
823 'StructuredChangeFiltersLiveUpdatePollingRate',
824 $this->
getConfig()->
get(
'StructuredChangeFiltersLiveUpdatePollingRate' )
827 $out->addBodyClasses(
'mw-rcfilters-disabled' );
839 return $cache->getWithSetCallback(
840 $cache->makeKey(
'changeslistspecialpage-changetags',
$context->getLanguage()->getCode() ),
841 $cache::TTL_MINUTE * 10,
854 $tagHitCounts = array_merge( $explicitlyDefinedTags, $softwareActivatedTags );
858 foreach ( $tagHitCounts
as $tagName => $hits ) {
861 isset( $explicitlyDefinedTags[ $tagName ] ) ||
862 isset( $softwareActivatedTags[ $tagName ] )
866 'label' => Sanitizer::stripAllTags(
871 $tagName, self::TAG_DESC_CHARACTER_LIMIT,
$context
873 'cssClass' => Sanitizer::escapeClass(
'mw-tag-' . $tagName ),
880 usort(
$result,
function ( $a, $b ) {
881 return strcasecmp( $a[
'label'], $b[
'label'] );
897 '<div class="mw-changeslist-empty">' .
898 $this->
msg(
'recentchanges-noresult' )->parse() .
908 '<div class="mw-changeslist-empty mw-changeslist-timeout">' .
909 $this->
msg(
'recentchanges-timeout' )->parse() .
927 $this->
buildQuery(
$tables, $fields, $conds, $query_options, $join_conds, $opts );
929 return $this->
doMainQuery(
$tables, $fields, $conds, $query_options, $join_conds, $opts );
938 if ( $this->rcOptions ===
null ) {
939 $this->rcOptions = $this->
setup( $this->rcSubpage );
967 if ( $this->
getConfig()->
get(
'RCWatchCategoryMembership' ) ) {
969 $this->hideCategorizationFilterDefinition
972 $transformedHideCategorizationDef[
'group'] = $changeTypeGroup;
975 $transformedHideCategorizationDef
979 Hooks::run(
'ChangesListSpecialPageStructuredFilters', [ $this ] );
981 $unstructuredGroupDefinition =
988 $registered = $userExperienceLevel->getFilter(
'registered' );
989 $registered->setAsSupersetOf( $userExperienceLevel->getFilter(
'newcomer' ) );
990 $registered->setAsSupersetOf( $userExperienceLevel->getFilter(
'learner' ) );
991 $registered->setAsSupersetOf( $userExperienceLevel->getFilter(
'experienced' ) );
993 $categoryFilter = $changeTypeGroup->getFilter(
'hidecategorization' );
994 $logactionsFilter = $changeTypeGroup->getFilter(
'hidelog' );
995 $pagecreationFilter = $changeTypeGroup->getFilter(
'hidenewpages' );
998 $hideMinorFilter = $significanceTypeGroup->getFilter(
'hideminor' );
1001 if ( $categoryFilter !==
null ) {
1002 $hideMinorFilter->conflictsWith(
1004 'rcfilters-hideminor-conflicts-typeofchange-global',
1005 'rcfilters-hideminor-conflicts-typeofchange',
1006 'rcfilters-typeofchange-conflicts-hideminor'
1009 $hideMinorFilter->conflictsWith(
1011 'rcfilters-hideminor-conflicts-typeofchange-global',
1012 'rcfilters-hideminor-conflicts-typeofchange',
1013 'rcfilters-typeofchange-conflicts-hideminor'
1015 $hideMinorFilter->conflictsWith(
1016 $pagecreationFilter,
1017 'rcfilters-hideminor-conflicts-typeofchange-global',
1018 'rcfilters-hideminor-conflicts-typeofchange',
1019 'rcfilters-typeofchange-conflicts-hideminor'
1033 return $filterDefinition;
1046 $autoFillPriority = -1;
1047 foreach ( $definition
as $groupDefinition ) {
1048 if ( !isset( $groupDefinition[
'priority'] ) ) {
1049 $groupDefinition[
'priority'] = $autoFillPriority;
1052 $autoFillPriority = $groupDefinition[
'priority'];
1055 $autoFillPriority--;
1057 $className = $groupDefinition[
'class'];
1058 unset( $groupDefinition[
'class'] );
1060 foreach ( $groupDefinition[
'filters']
as &$filterDefinition ) {
1076 $unstructuredGroupDefinition = [
1077 'name' =>
'unstructured',
1084 $unstructuredGroupDefinition[
'filters'][] = [
1087 'default' =>
$params[
'default'],
1091 return $unstructuredGroupDefinition;
1099 foreach ( $this->filterGroups
as $group ) {
1101 foreach ( $group->getFilters()
as $key => $filter ) {
1102 if ( $filter->displaysOnUnstructuredUi( $this ) ) {
1103 $filters[ $key ] = $filter;
1127 if ( $parameters !==
null ) {
1149 $useDefaults = $this->
getRequest()->getInt(
'urlversion' ) !== 2;
1152 foreach ( $this->filterGroups
as $filterGroup ) {
1153 $filterGroup->addOptions( $opts, $useDefaults, $structuredUI );
1157 $opts->add(
'invert',
false );
1158 $opts->add(
'associated',
false );
1159 $opts->add(
'urlversion', 1 );
1160 $opts->add(
'tagfilter',
'' );
1165 $opts->add(
'from',
'' );
1176 $groupName = $group->
getName();
1178 $this->filterGroups[$groupName] = $group;
1198 return isset( $this->filterGroups[$groupName] ) ?
1199 $this->filterGroups[$groupName] :
1218 'messageKeys' => [],
1221 usort( $this->filterGroups,
function ( $a, $b ) {
1222 return $b->getPriority() - $a->getPriority();
1225 foreach ( $this->filterGroups
as $groupName => $group ) {
1226 $groupOutput = $group->getJsData( $this );
1227 if ( $groupOutput !==
null ) {
1228 $output[
'messageKeys'] = array_merge(
1230 $groupOutput[
'messageKeys']
1233 unset( $groupOutput[
'messageKeys'] );
1234 $output[
'groups'][] = $groupOutput;
1248 if ( $this->customFilters ===
null ) {
1249 $this->customFilters = [];
1250 Hooks::run(
'ChangesListSpecialPageFilters', [ $this, &$this->customFilters ],
'1.29' );
1265 $opts->fetchValuesFromRequest( $this->
getRequest() );
1277 $stringParameterNameSet = [];
1278 $hideParameterNameSet = [];
1283 foreach ( $this->filterGroups
as $filterGroup ) {
1285 $stringParameterNameSet[$filterGroup->getName()] =
true;
1287 foreach ( $filterGroup->getFilters()
as $filter ) {
1288 $hideParameterNameSet[$filter->getName()] =
true;
1293 $bits = preg_split(
'/\s*,\s*/', trim( $par ) );
1294 foreach ( $bits
as $bit ) {
1296 if ( isset( $hideParameterNameSet[$bit] ) ) {
1299 } elseif ( isset( $hideParameterNameSet[
"hide$bit"] ) ) {
1301 $opts[
"hide$bit"] =
false;
1302 } elseif ( preg_match(
'/^(.*)=(.*)$/', $bit, $m ) ) {
1303 if ( isset( $stringParameterNameSet[$m[1]] ) ) {
1304 $opts[$m[1]] = $m[2];
1319 if ( $isContradictory || $isReplaced ) {
1337 foreach ( $this->filterGroups
as $filterGroup ) {
1339 $filters = $filterGroup->getFilters();
1341 if (
count( $filters ) === 1 ) {
1346 $allInGroupEnabled = array_reduce(
1348 function ( $carry, $filter )
use ( $opts ) {
1349 return $carry && $opts[ $filter->getName() ];
1351 count( $filters ) > 0
1354 if ( $allInGroupEnabled ) {
1355 foreach ( $filters
as $filter ) {
1356 $opts[ $filter->getName() ] =
false;
1377 if ( $opts[
'hideanons'] && $opts[
'hideliu'] ) {
1378 $opts->
reset(
'hideanons' );
1379 if ( !$opts[
'hidebots'] ) {
1380 $opts->
reset(
'hideliu' );
1381 $opts[
'hidehumans'] = 1;
1405 if ( $opts[
'hideanons' ] ) {
1406 $opts->
reset(
'hideanons' );
1407 $opts[
'userExpLevel' ] =
'registered';
1411 if ( $opts[
'hideliu' ] ) {
1412 $opts->
reset(
'hideliu' );
1413 $opts[
'userExpLevel' ] =
'unregistered';
1418 if ( $opts[
'hidepatrolled' ] ) {
1419 $opts->
reset(
'hidepatrolled' );
1420 $opts[
'reviewStatus' ] =
'unpatrolled';
1424 if ( $opts[
'hideunpatrolled' ] ) {
1425 $opts->
reset(
'hideunpatrolled' );
1426 $opts[
'reviewStatus' ] = implode(
1428 [
'manual',
'auto' ]
1447 if (
$value ===
false ) {
1473 foreach ( $this->filterGroups
as $filterGroup ) {
1474 $filterGroup->modifyQuery(
$dbr, $this,
$tables, $fields, $conds,
1475 $query_options, $join_conds, $opts, $isStructuredUI );
1479 if ( $opts[
'namespace' ] !==
'' ) {
1480 $namespaces = explode(
';', $opts[
'namespace' ] );
1482 if ( $opts[
'associated' ] ) {
1483 $associatedNamespaces = array_map(
1493 $operator = $opts[
'invert' ] ?
'!=' :
'=';
1496 $operator = $opts[
'invert' ] ?
'NOT IN' :
'IN';
1500 $conds[] =
"rc_namespace $operator $value";
1504 $cutoff_unixtime = time() - $opts[
'days'] * 3600 * 24;
1505 $cutoff =
$dbr->timestamp( $cutoff_unixtime );
1507 $fromValid = preg_match(
'/^[0-9]{14}$/', $opts[
'from'] );
1508 if ( $fromValid && $opts[
'from'] >
wfTimestamp( TS_MW, $cutoff ) ) {
1509 $cutoff =
$dbr->timestamp( $opts[
'from'] );
1511 $opts->
reset(
'from' );
1514 $conds[] =
'rc_timestamp >= ' .
$dbr->addQuotes( $cutoff );
1533 $fields = array_merge( $rcQuery[
'fields'], $fields );
1534 $join_conds = array_merge( $join_conds, $rcQuery[
'joins'] );
1553 return $dbr->select(
1564 &$query_options, &$join_conds, $opts
1567 'ChangesListSpecialPageQuery',
1568 [ $this->
getName(), &
$tables, &$fields, &$conds, &$query_options, &$join_conds, $opts ]
1590 $this->
doHeader( $opts, $rowCount );
1676 # The legend showing what the letters and stuff mean
1678 # Iterates through them and gets the messages for both letter and tooltip
1679 $legendItems =
$context->getConfig()->get(
'RecentChangesFlags' );
1680 if ( !(
$user->useRCPatrol() ||
$user->useNPPatrol() ) ) {
1681 unset( $legendItems[
'unpatrolled'] );
1683 foreach ( $legendItems
as $key => $item ) { # generate items
of the legend
1684 $label = isset( $item[
'legend'] ) ? $item[
'legend'] : $item[
'title'];
1685 $letter = $item[
'letter'];
1686 $cssClass = isset( $item[
'class'] ) ? $item[
'class'] : $key;
1689 [
'class' => $cssClass ],
$context->msg( $letter )->text()
1692 [
'class' => Sanitizer::escapeClass(
'mw-changeslist-legend-' . $key ) ],
1698 [
'class' =>
'mw-plusminus-pos' ],
1699 $context->msg(
'recentchanges-legend-plusminus' )->parse()
1703 [
'class' =>
'mw-changeslist-legend-plusminus' ],
1704 $context->msg(
'recentchanges-label-plusminus' )->text()
1709 $context->msg(
'rcfilters-legend-heading' )->parse() :
1710 $context->msg(
'recentchanges-legend-heading' )->parse();
1713 $collapsedState = $this->
getRequest()->getCookie(
'changeslist-state' );
1714 $collapsedClass = $collapsedState ===
'collapsed' ?
' mw-collapsed' :
'';
1717 '<div class="mw-changeslist-legend mw-collapsible' . $collapsedClass .
'">' .
1719 '<div class="mw-collapsible-content">' . $legend .
'</div>' .
1731 $out->addModuleStyles( [
1732 'mediawiki.special.changeslist.legend',
1733 'mediawiki.special.changeslist',
1735 $out->addModules(
'mediawiki.special.changeslist.legend.js' );
1738 $out->addModules(
'mediawiki.rcfilters.filters.ui' );
1739 $out->addModuleStyles(
'mediawiki.rcfilters.filters.base.styles' );
1764 &
$tables, &$fields, &$conds, &$query_options, &$join_conds, $selectedExpLevels, $now = 0
1774 if (
count( $selectedExpLevels ) === $LEVEL_COUNT ) {
1780 in_array(
'registered', $selectedExpLevels ) &&
1781 in_array(
'unregistered', $selectedExpLevels )
1787 $actorQuery = $actorMigration->getJoin(
'rc_user' );
1788 $tables += $actorQuery[
'tables'];
1789 $join_conds += $actorQuery[
'joins'];
1793 in_array(
'registered', $selectedExpLevels ) &&
1794 !in_array(
'unregistered', $selectedExpLevels )
1796 $conds[] = $actorMigration->isNotAnon( $actorQuery[
'fields'][
'rc_user'] );
1800 if ( $selectedExpLevels === [
'unregistered' ] ) {
1801 $conds[] = $actorMigration->isAnon( $actorQuery[
'fields'][
'rc_user'] );
1806 $join_conds[
'user'] = [
'LEFT JOIN', $actorQuery[
'fields'][
'rc_user'] .
' = user_id' ];
1811 $secondsPerDay = 86400;
1815 $aboveNewcomer =
$dbr->makeList(
1818 'user_registration <= ' .
$dbr->addQuotes(
$dbr->timestamp( $learnerCutoff ) ),
1823 $aboveLearner =
$dbr->makeList(
1826 'user_registration <= ' .
1827 $dbr->addQuotes(
$dbr->timestamp( $experiencedUserCutoff ) ),
1834 if ( in_array(
'unregistered', $selectedExpLevels ) ) {
1835 $selectedExpLevels = array_diff( $selectedExpLevels, [
'unregistered' ] );
1836 $conditions[] = $actorMigration->isAnon( $actorQuery[
'fields'][
'rc_user'] );
1839 if ( $selectedExpLevels === [
'newcomer' ] ) {
1840 $conditions[] =
"NOT ( $aboveNewcomer )";
1841 } elseif ( $selectedExpLevels === [
'learner' ] ) {
1842 $conditions[] =
$dbr->makeList(
1843 [ $aboveNewcomer,
"NOT ( $aboveLearner )" ],
1846 } elseif ( $selectedExpLevels === [
'experienced' ] ) {
1847 $conditions[] = $aboveLearner;
1848 } elseif ( $selectedExpLevels === [
'learner',
'newcomer' ] ) {
1849 $conditions[] =
"NOT ( $aboveLearner )";
1850 } elseif ( $selectedExpLevels === [
'experienced',
'newcomer' ] ) {
1851 $conditions[] =
$dbr->makeList(
1852 [
"NOT ( $aboveNewcomer )", $aboveLearner ],
1855 } elseif ( $selectedExpLevels === [
'experienced',
'learner' ] ) {
1856 $conditions[] = $aboveNewcomer;
1857 } elseif ( $selectedExpLevels === [
'experienced',
'learner',
'newcomer' ] ) {
1858 $conditions[] = $actorMigration->isNotAnon( $actorQuery[
'fields'][
'rc_user'] );
1861 if (
count( $conditions ) > 1 ) {
1863 } elseif (
count( $conditions ) === 1 ) {
1864 $conds[] = reset( $conditions );
1874 if ( $this->
getRequest()->getBool(
'rcfilters' ) ) {
1878 return static::checkStructuredFilterUiEnabled(
1891 if ( $this->
getConfig()->
get(
'StructuredChangeFiltersShowPreference' ) ) {
1892 return !$this->
getUser()->getDefaultOption(
'rcenhancedfilters-disable' );
1894 return $this->
getUser()->getDefaultOption(
'rcenhancedfilters' );
1907 if ( $config->
get(
'StructuredChangeFiltersShowPreference' ) ) {
1908 return !
$user->getOption(
'rcenhancedfilters-disable' );
1910 return $user->getOption(
'rcenhancedfilters' );
1922 return $this->
getUser()->getIntOption( static::$limitPreferenceName );
1934 return floatval( $this->
getUser()->getOption( static::$daysPreferenceName ) );