"\nBacktrace:\n" .
MWExceptionHandler::getRedactedTraceAsString( $e ) . "\n";
} else {
- return self::getShowBacktraceError( $e );
+ return self::getShowBacktraceError( $e ) . "\n";
}
}
$vars[] = '$wgShowDBErrorBacktrace = true;';
}
$vars = implode( ' and ', $vars );
- return "Set $vars at the bottom of LocalSettings.php to show detailed debugging information\n";
+ return "Set $vars at the bottom of LocalSettings.php to show detailed debugging information.";
}
/**
return "$select<br />\n$textbox";
}
+ protected function getOOUIModules() {
+ return [ 'mediawiki.widgets.SelectWithInputWidget' ];
+ }
+
public function getInputOOUI( $value ) {
- return false;
+ $this->mParent->getOutput()->addModuleStyles( 'mediawiki.widgets.SelectWithInputWidget.styles' );
+
+ # TextInput
+ $textAttribs = [
+ 'id' => $this->mID . '-other',
+ 'name' => $this->mName . '-other',
+ 'size' => $this->getSize(),
+ 'class' => [ 'mw-htmlform-select-and-other-field' ],
+ 'data-id-select' => $this->mID,
+ 'value' => $value[2],
+ ];
+
+ $allowedParams = [
+ 'required',
+ 'autofocus',
+ 'multiple',
+ 'disabled',
+ 'tabindex',
+ 'maxlength',
+ ];
+
+ $textAttribs += OOUI\Element::configFromHtmlAttributes(
+ $this->getAttributes( $allowedParams )
+ );
+
+ if ( $this->mClass !== '' ) {
+ $textAttribs['classes'] = [ $this->mClass ];
+ }
+
+ # DropdownInput
+ $dropdownInputAttribs = [
+ 'name' => $this->mName,
+ 'id' => $this->mID,
+ 'options' => $this->getOptionsOOUI(),
+ 'value' => $value[1],
+ ];
+
+ $allowedParams = [
+ 'tabindex',
+ 'disabled',
+ ];
+
+ $dropdownInputAttribs += OOUI\Element::configFromHtmlAttributes(
+ $this->getAttributes( $allowedParams )
+ );
+
+ if ( $this->mClass !== '' ) {
+ $dropdownInputAttribs['classes'] = [ $this->mClass ];
+ }
+
+ return $this->getInputWidget( [
+ 'textinput' => $textAttribs,
+ 'dropdowninput' => $dropdownInputAttribs,
+ 'or' => false,
+ ] );
+ }
+
+ public function getInputWidget( $params ) {
+ return new Mediawiki\Widget\SelectWithInputWidget( $params );
}
/**
return "$select<br />\n$textbox";
}
+ protected function shouldInfuseOOUI() {
+ return true;
+ }
+
+ protected function getOOUIModules() {
+ return [ 'mediawiki.widgets.SelectWithInputWidget' ];
+ }
+
public function getInputOOUI( $value ) {
- return false;
+ $this->mParent->getOutput()->addModuleStyles( 'mediawiki.widgets.SelectWithInputWidget.styles' );
+
+ $valInSelect = false;
+ if ( $value !== false ) {
+ $value = strval( $value );
+ $valInSelect = in_array(
+ $value, HTMLFormField::flattenOptions( $this->getOptions() ), true
+ );
+ }
+
+ # DropdownInput
+ $dropdownAttribs = [
+ 'id' => $this->mID,
+ 'name' => $this->mName,
+ 'options' => $this->getOptionsOOUI(),
+ 'value' => $valInSelect ? $value : 'other',
+ 'class' => [ 'mw-htmlform-select-or-other' ],
+ ];
+
+ $allowedParams = [
+ 'disabled',
+ 'tabindex',
+ ];
+
+ $dropdownAttribs += OOUI\Element::configFromHtmlAttributes(
+ $this->getAttributes( $allowedParams )
+ );
+
+ # TextInput
+ $textAttribs = [
+ 'id' => $this->mID . '-other',
+ 'name' => $this->mName . '-other',
+ 'size' => $this->getSize(),
+ 'value' => $valInSelect ? '' : $value,
+ ];
+
+ $allowedParams = [
+ 'required',
+ 'autofocus',
+ 'multiple',
+ 'disabled',
+ 'tabindex',
+ 'maxlength',
+ ];
+
+ $textAttribs += OOUI\Element::configFromHtmlAttributes(
+ $this->getAttributes( $allowedParams )
+ );
+
+ if ( $this->mClass !== '' ) {
+ $textAttribs['classes'] = [ $this->mClass ];
+ }
+ if ( $this->mPlaceholder !== '' ) {
+ $textAttribs['placeholder'] = $this->mPlaceholder;
+ }
+
+ return $this->getInputWidget( [
+ 'textinput' => $textAttribs,
+ 'dropdowninput' => $dropdownAttribs,
+ 'or' => true,
+ ] );
+ }
+
+ public function getInputWidget( $params ) {
+ return new Mediawiki\Widget\SelectWithInputWidget( $params );
}
/**
protected function getDB( $index ) {
$lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
$lb = ( $this->cluster !== false )
- ? $lbFactory->getExternalLB( $this->cluster, $this->wiki )
+ ? $lbFactory->getExternalLB( $this->cluster )
: $lbFactory->getMainLB( $this->wiki );
return $lb->getConnectionRef( $index, [], $this->wiki );
'filters' => [
[
'name' => 'hideliu',
- 'label' => 'rcfilters-filter-registered-label',
- 'description' => 'rcfilters-filter-registered-description',
// rcshowhideliu-show, rcshowhideliu-hide,
// wlshowhideliu
'showHideSuffix' => 'showhideliu',
) {
$conds[] = 'rc_user = 0';
},
- 'cssClassSuffix' => 'liu',
- 'isRowApplicableCallable' => function ( $ctx, $rc ) {
- return $rc->getAttribute( 'rc_user' );
- },
+ 'isReplacedInStructuredUi' => true,
],
[
'name' => 'hideanons',
- 'label' => 'rcfilters-filter-unregistered-label',
- 'description' => 'rcfilters-filter-unregistered-description',
// rcshowhideanons-show, rcshowhideanons-hide,
// wlshowhideanons
'showHideSuffix' => 'showhideanons',
) {
$conds[] = 'rc_user != 0';
},
- 'cssClassSuffix' => 'anon',
- 'isRowApplicableCallable' => function ( $ctx, $rc ) {
- return !$rc->getAttribute( 'rc_user' );
- },
+ 'isReplacedInStructuredUi' => true,
]
],
],
'name' => 'userExpLevel',
'title' => 'rcfilters-filtergroup-userExpLevel',
'class' => ChangesListStringOptionsFilterGroup::class,
- // Excludes unregistered users
- 'isFullCoverage' => false,
+ 'isFullCoverage' => true,
'filters' => [
+ [
+ 'name' => 'unregistered',
+ 'label' => 'rcfilters-filter-user-experience-level-unregistered-label',
+ 'description' => 'rcfilters-filter-user-experience-level-unregistered-description',
+ 'cssClassSuffix' => 'user-unregistered',
+ 'isRowApplicableCallable' => function ( $ctx, $rc ) {
+ return !$rc->getAttribute( 'rc_user' );
+ }
+ ],
+ [
+ 'name' => 'registered',
+ 'label' => 'rcfilters-filter-user-experience-level-registered-label',
+ 'description' => 'rcfilters-filter-user-experience-level-registered-description',
+ 'cssClassSuffix' => 'user-registered',
+ 'isRowApplicableCallable' => function ( $ctx, $rc ) {
+ return $rc->getAttribute( 'rc_user' );
+ }
+ ],
[
'name' => 'newcomer',
'label' => 'rcfilters-filter-user-experience-level-newcomer-label',
$this->registerFiltersFromDefinitions( [ $unstructuredGroupDefinition ] );
$userExperienceLevel = $this->getFilterGroup( 'userExpLevel' );
-
- $registration = $this->getFilterGroup( 'registration' );
- $anons = $registration->getFilter( 'hideanons' );
-
- // This means there is a conflict between any item in user experience level
- // being checked and only anons being *shown* (hideliu=1&hideanons=0 in the
- // URL, or equivalent).
- $userExperienceLevel->conflictsWith(
- $anons,
- 'rcfilters-filtergroup-user-experience-level-conflicts-unregistered-global',
- 'rcfilters-filtergroup-user-experience-level-conflicts-unregistered',
- 'rcfilters-filter-unregistered-conflicts-user-experience-level'
- );
+ $registered = $userExperienceLevel->getFilter( 'registered' );
+ $registered->setAsSupersetOf( $userExperienceLevel->getFilter( 'newcomer' ) );
+ $registered->setAsSupersetOf( $userExperienceLevel->getFilter( 'learner' ) );
+ $registered->setAsSupersetOf( $userExperienceLevel->getFilter( 'experienced' ) );
$categoryFilter = $changeTypeGroup->getFilter( 'hidecategorization' );
$logactionsFilter = $changeTypeGroup->getFilter( 'hidelog' );
$wgLearnerMemberSince,
$wgExperiencedUserMemberSince;
- $LEVEL_COUNT = 3;
+ $LEVEL_COUNT = 5;
- // If all levels are selected, all logged-in users are included (but no
- // anons), so we can short-circuit.
+ // If all levels are selected, don't filter
if ( count( $selectedExpLevels ) === $LEVEL_COUNT ) {
+ return;
+ }
+
+ // both 'registered' and 'unregistered', experience levels, if any, are included in 'registered'
+ if (
+ in_array( 'registered', $selectedExpLevels ) &&
+ in_array( 'unregistered', $selectedExpLevels )
+ ) {
+ return;
+ }
+
+ // 'registered' but not 'unregistered', experience levels, if any, are included in 'registered'
+ if (
+ in_array( 'registered', $selectedExpLevels ) &&
+ !in_array( 'unregistered', $selectedExpLevels )
+ ) {
$conds[] = 'rc_user != 0';
return;
}
+ if ( $selectedExpLevels === [ 'unregistered' ] ) {
+ $conds[] = 'rc_user = 0';
+ return;
+ }
+
$tables[] = 'user';
$join_conds['user'] = [ 'LEFT JOIN', 'rc_user = user_id' ];
IDatabase::LIST_AND
);
+ $conditions = [];
+
+ if ( in_array( 'unregistered', $selectedExpLevels ) ) {
+ $selectedExpLevels = array_diff( $selectedExpLevels, [ 'unregistered' ] );
+ $conditions[] = 'rc_user = 0';
+ }
+
if ( $selectedExpLevels === [ 'newcomer' ] ) {
- $conds[] = "NOT ( $aboveNewcomer )";
+ $conditions[] = "NOT ( $aboveNewcomer )";
} elseif ( $selectedExpLevels === [ 'learner' ] ) {
- $conds[] = $dbr->makeList(
+ $conditions[] = $dbr->makeList(
[ $aboveNewcomer, "NOT ( $aboveLearner )" ],
IDatabase::LIST_AND
);
} elseif ( $selectedExpLevels === [ 'experienced' ] ) {
- $conds[] = $aboveLearner;
+ $conditions[] = $aboveLearner;
} elseif ( $selectedExpLevels === [ 'learner', 'newcomer' ] ) {
- $conds[] = "NOT ( $aboveLearner )";
+ $conditions[] = "NOT ( $aboveLearner )";
} elseif ( $selectedExpLevels === [ 'experienced', 'newcomer' ] ) {
- $conds[] = $dbr->makeList(
+ $conditions[] = $dbr->makeList(
[ "NOT ( $aboveNewcomer )", $aboveLearner ],
IDatabase::LIST_OR
);
} elseif ( $selectedExpLevels === [ 'experienced', 'learner' ] ) {
- $conds[] = $aboveNewcomer;
+ $conditions[] = $aboveNewcomer;
+ } elseif ( $selectedExpLevels === [ 'experienced', 'learner', 'newcomer' ] ) {
+ $conditions[] = 'rc_user != 0';
+ }
+
+ if ( count( $conditions ) > 1 ) {
+ $conds[] = $dbr->makeList( $conditions, IDatabase::LIST_OR );
+ } elseif ( count( $conditions ) === 1 ) {
+ $conds[] = reset( $conditions );
}
}
}
"rcfilters-noresults-conflict": "No results found because the search criteria are in conflict",
"rcfilters-state-message-subset": "This filter has no effect because its results are included with those of the following, broader {{PLURAL:$2|filter|filters}} (try highlighting to distinguish it): $1",
"rcfilters-state-message-fullcoverage": "Selecting all filters in a group is the same as selecting none, so this filter has no effect. Group includes: $1",
- "rcfilters-filtergroup-registration": "User registration",
- "rcfilters-filter-registered-label": "Registered",
- "rcfilters-filter-registered-description": "Logged-in editors.",
- "rcfilters-filter-unregistered-label": "Unregistered",
- "rcfilters-filter-unregistered-description": "Editors who aren’t logged in.",
- "rcfilters-filter-unregistered-conflicts-user-experience-level": "This filter conflicts with the following Experience {{PLURAL:$2|filter|filters}}, which {{PLURAL:$2|finds|find}} only registered users: $1",
"rcfilters-filtergroup-authorship": "Contribution authorship",
"rcfilters-filter-editsbyself-label": "Changes by you",
"rcfilters-filter-editsbyself-description": "Your own contributions.",
"rcfilters-filter-editsbyother-label": "Changes by others",
"rcfilters-filter-editsbyother-description": "All changes except your own.",
- "rcfilters-filtergroup-userExpLevel": "Experience level (for registered users only)",
- "rcfilters-filtergroup-user-experience-level-conflicts-unregistered": "Experience filters find only registered users, so this filter conflicts with the “Unregistered” filter.",
- "rcfilters-filtergroup-user-experience-level-conflicts-unregistered-global": "The \"Unregistered\" filter conflicts with one or more Experience filters, which find registered users only. The conflicting filters are marked in the Active Filters area, above.",
+ "rcfilters-filtergroup-userExpLevel": "Experience registration and experience",
+ "rcfilters-filter-user-experience-level-registered-label": "Registered",
+ "rcfilters-filter-user-experience-level-registered-description": "Logged-in editors.",
+ "rcfilters-filter-user-experience-level-unregistered-label": "Unregistered",
+ "rcfilters-filter-user-experience-level-unregistered-description": "Editors who aren't logged-in.",
"rcfilters-filter-user-experience-level-newcomer-label": "Newcomers",
- "rcfilters-filter-user-experience-level-newcomer-description": "Fewer than 10 edits and 4 days of activity.",
+ "rcfilters-filter-user-experience-level-newcomer-description": "Registered editors with fewer than 10 edits and 4 days of activity.",
"rcfilters-filter-user-experience-level-learner-label": "Learners",
- "rcfilters-filter-user-experience-level-learner-description": "More experience than \"Newcomers\" but less than \"Experienced users\".",
+ "rcfilters-filter-user-experience-level-learner-description": "Registered editors whose experience falls between \"Newcomers\" and \"Experienced users.\"",
"rcfilters-filter-user-experience-level-experienced-label": "Experienced users",
- "rcfilters-filter-user-experience-level-experienced-description": "More than 30 days of activity and 500 edits.",
+ "rcfilters-filter-user-experience-level-experienced-description": "Registered editors with more than 500 edits and 30 days of activity.",
"rcfilters-filtergroup-automated": "Automated contributions",
"rcfilters-filter-bots-label": "Bot",
"rcfilters-filter-bots-description": "Edits made by automated tools.",
"rcfilters-noresults-conflict": "A message displayed in the results area when no results found because there are filters in conflict with one another.",
"rcfilters-state-message-subset": "Tooltip shown when hovering over a filter tag when one or more broader filters that contain the hovered filter are also selected. This indicates that the hovered filter has no effect because all the results it matches are also matched by the broader filter(s). Parameters:\n* $1 - Comma-separated string of selected broader filters that this filter is a subset of\n* $2 - Count of filters in $1, for PLURAL",
"rcfilters-state-message-fullcoverage": "Tooltip shown when hovering over a filter tag when all the filters in its group are selected. This indicates that the hovered filter has no effect because the selected filters in the group cover all changes. Parameters:\n* $1 - Comma-separated string of selected filters in the group\n* $2 - Count of filters in $1, for PLURAL",
- "rcfilters-filtergroup-registration": "Title for the filter group for editor registration type.",
- "rcfilters-filter-registered-label": "Label for the filter for showing edits made by logged-in users.\n{{Identical|Registered}}",
- "rcfilters-filter-registered-description": "Description for the filter for showing edits made by logged-in users.",
- "rcfilters-filter-unregistered-label": "Label for the filter for showing edits made by logged-out users.\n{{Identical|Unregistered}}",
- "rcfilters-filter-unregistered-description": "Description for the filter for showing edits made by logged-out users.",
- "rcfilters-filter-unregistered-conflicts-user-experience-level": "Tooltip shown when hovering over a Unregistered filter tag, when a User Experience Level filter is also selected.\n\n\"Unregistered\" is {{msg-mw|Rcfilters-filter-unregistered-label}}.\n\n\"Experience\" is based on {{msg-mw|Rcfilters-filtergroup-userExpLevel}}.\n\nThis indicates that no results will be shown, because users matched by the User Experience Level groups are never unregistered. Parameters:\n* $1 - Comma-separated string of selected User Experience Level filters, e.g. \"Newcomer, Experienced\"\n* $2 - Count of selected User Experience Level filters, for PLURAL",
"rcfilters-filtergroup-authorship": "Title for the filter group for edit authorship. This filter group allows the user to choose between \"Your own edits\" and \"Edits by others\". More info: https://phabricator.wikimedia.org/T149859",
"rcfilters-filter-editsbyself-label": "Label for the filter for showing edits made by the current user.",
"rcfilters-filter-editsbyself-description": "Description for the filter for showing edits made by the current user.",
"rcfilters-filter-editsbyother-label": "Label for the filter for showing edits made by anyone other than the current user.",
"rcfilters-filter-editsbyother-description": "Description for the filter for showing edits made by anyone other than the current user.",
"rcfilters-filtergroup-userExpLevel": "Title for the filter group for user experience levels.",
- "rcfilters-filtergroup-user-experience-level-conflicts-unregistered": "Tooltip shown when hovering over a User Experience Level filter tag, when only Unregistered users are being shown. This indicates that no results will be shown, because users matched by the User Experience Level groups are never unregistered.\n\n\"Unregistered\" is {{msg-mw|Rcfilters-filter-unregistered-label}}.",
- "rcfilters-filtergroup-user-experience-level-conflicts-unregistered-global": "Message shown in the result area when both a User Experience Level filter and the Unregistered filter are selected. This indicates that no results will be shown because users selected by the User Experience Filter are never unregistered.\n\n\"Unregistered\" is {{msg-mw|Rcfilters-filter-unregistered-label}}.\n\n\"Experience\" is based on {{msg-mw|Rcfilters-filtergroup-userExpLevel}}.",
+ "rcfilters-filter-user-experience-level-registered-label": "Label for the filter for showing edits made by logged-in editors.",
+ "rcfilters-filter-user-experience-level-registered-description": "Description for the filter for showing edits made by logged-in editors.",
+ "rcfilters-filter-user-experience-level-unregistered-label": "Label for the filter for showing edits made by anonymous editors.",
+ "rcfilters-filter-user-experience-level-unregistered-description": "Description for the filter for showing edits made by anonymous editors.",
"rcfilters-filter-user-experience-level-newcomer-label": "Label for the filter for showing edits made by new editors.",
"rcfilters-filter-user-experience-level-newcomer-description": "Description for the filter for showing edits made by new editors.",
"rcfilters-filter-user-experience-level-learner-label": "Label for the filter for showing edits made by learning editors.",
parent::__construct();
$this->addDescription( 'Deletes all pages in the MediaWiki namespace' .
' which were last edited by "MediaWiki default"' );
+ $this->addOption( 'dry-run', 'Perform a dry run, delete nothing' );
}
public function execute() {
);
if ( $dbr->numRows( $res ) == 0 ) {
- # No more messages left
+ // No more messages left
$this->output( "done.\n" );
+ return;
+ }
+ $dryrun = $this->hasOption( 'dry-run' );
+ if ( $dryrun ) {
+ foreach ( $res as $row ) {
+ $title = Title::makeTitle( $row->page_namespace, $row->page_title );
+ $this->output( "\n* [[$title]]" );
+ }
+ $this->output( "\n\nRun again without --dry-run to delete these pages.\n" );
return;
}
- # Deletions will be made by $user temporarly added to the bot group
- # in order to hide it in RecentChanges.
+ // Deletions will be made by $user temporarly added to the bot group
+ // in order to hide it in RecentChanges.
$user = User::newFromName( 'MediaWiki default' );
if ( !$user ) {
$this->error( "Invalid username", true );
$user->addGroup( 'bot' );
$wgUser = $user;
- # Handle deletion
+ // Handle deletion
$this->output( "\n...deleting old default messages (this may take a long time!)...", 'msg' );
$dbw = $this->getDB( DB_MASTER );
function syncText() {
var value = $( this ).val()
- .replace( /[\[\]\{\}|#<>%+? ]/g, '_' )
+ .replace( /[\[\]{}|#<>%+? ]/g, '_' ) // eslint-disable-line no-useless-escape
.replace( /&/, '&' )
.replace( /__+/g, '_' )
.replace( /^_+/, '' )
}
// Set up the help system
- $( '.config-help-field-data' )
- .hide()
- .closest( '.config-help-field-container' )
- .find( '.config-help-field-hint' )
- .show()
- .click( function () {
- $( this )
- .closest( '.config-help-field-container' )
- .find( '.config-help-field-data' )
- .slideToggle( 'fast' );
- } );
+ $( '.config-help-field-data' ).hide()
+ .closest( '.config-help-field-container' ).find( '.config-help-field-hint' )
+ .show()
+ .click( function () {
+ $( this ).closest( '.config-help-field-container' ).find( '.config-help-field-data' )
+ .slideToggle( 'fast' );
+ } );
// Show/hide code for DB-specific options
// FIXME: Do we want slow, fast, or even non-animated (instantaneous) showing/hiding here?
"grunt-banana-checker": "0.6.0",
"grunt-contrib-copy": "1.0.0",
"grunt-contrib-watch": "1.0.0",
- "grunt-eslint": "19.0.0",
+ "grunt-eslint": "20.0.0",
"grunt-jsonlint": "1.1.0",
"grunt-karma": "2.0.0",
"grunt-stylelint": "0.8.0",
if ( $badge.length ) {
$badge
.toggleClass( 'mw-badge-important', isImportant )
- .find( '.mw-badge-content' )
- .text( text );
+ .find( '.mw-badge-content' ).text( text );
} else {
// Otherwise, create a new badge with the specified text and style
$badge = $( '<div class="mw-badge"></div>' )
// Look for rgb(num%,num%,num%)
// eslint-disable-next-line no-cond-assign
- if ( result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec( color ) ) {
+ if ( result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)%\s*,\s*([0-9]+(?:\.[0-9]+)?)%\s*,\s*([0-9]+(?:\.[0-9]+)?)%\s*\)/.exec( color ) ) {
return [
parseFloat( result[ 1 ] ) * 2.55,
parseFloat( result[ 2 ] ) * 2.55,
buildDefaultToggleLink = function () {
return $( '<a class="mw-collapsible-text"></a>' )
.text( collapseText )
- .wrap( '<span class="mw-collapsible-toggle"></span>' ).parent()
- .attr( {
- role: 'button',
- tabindex: 0
- } )
- .prepend( '<span>[</span>' )
- .append( '<span>]</span>' )
- .on( 'click.mw-collapsible keypress.mw-collapsible', actionHandler );
+ .wrap( '<span class="mw-collapsible-toggle"></span>' )
+ .parent()
+ .attr( {
+ role: 'button',
+ tabindex: 0
+ } )
+ .prepend( '<span>[</span>' )
+ .append( '<span>]</span>' )
+ .on( 'click.mw-collapsible keypress.mw-collapsible', actionHandler );
};
// Check if this element has a custom position for the toggle link
},
trimRight: function ( str ) {
return str === null ?
- '' : str.toString().replace( /\s+$/, '' );
+ '' : str.toString().replace( /\s+$/, '' );
},
ucFirst: function ( str ) {
return str.charAt( 0 ).toUpperCase() + str.slice( 1 );
} );
mw.log.deprecate( $, 'escapeRE', function ( str ) {
- return str.replace( /([\\{}()|.?*+\-\^$\[\]])/g, '\\$1' );
+ return str.replace( /([\\{}()|.?*+\-^$\[\]])/g, '\\$1' ); // eslint-disable-line no-useless-escape
}, 'Use mediawiki.RegExp instead.' );
}( jQuery, mediaWiki ) );
* @class jQuery.plugin.suggestions
*/
- // jscs:disable checkParamNames
/**
* @method suggestions
* @chainable
* @param {boolean} [options.highlightInput=false] Whether to highlight matched portions of the
* input or not.
*/
- // jscs:enable checkParamNames
( function ( $, mw ) {
27, // escape
13, // enter
46, // delete
- 8 // backspace
+ 8 // backspace
];
if ( context.data.keypressedCount === 0 &&
e.which === context.data.keypressed &&
// Build RegEx
// Any date formated with . , ' - or /
- ts.dateRegex[ 0 ] = new RegExp( /^\s*(\d{1,2})[\,\.\-\/'\s]{1,2}(\d{1,2})[\,\.\-\/'\s]{1,2}(\d{2,4})\s*?/i );
+ ts.dateRegex[ 0 ] = new RegExp( /^\s*(\d{1,2})[,.\-/'\s]{1,2}(\d{1,2})[,.\-/'\s]{1,2}(\d{2,4})\s*?/i );
// Written Month name, dmy
ts.dateRegex[ 1 ] = new RegExp(
}
columnToCell = [];
- cellsInRow = ( $row[ 0 ].cells.length ) || 0; // all cells in this row
+ cellsInRow = ( $row[ 0 ].cells.length ) || 0; // all cells in this row
index = 0; // real cell index in this row
for ( j = 0; j < columns; index++ ) {
if ( index === cellsInRow ) {
}
ts.rgx = {
IPAddress: [
- new RegExp( /^\d{1,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$/ )
+ new RegExp( /^\d{1,3}[.]\d{1,3}[.]\d{1,3}[.]\d{1,3}$/ )
],
currency: [
new RegExp( /(^[£$€¥]|[£$€¥]$)/ ),
new RegExp( /(https?|ftp|file):\/\// )
],
isoDate: [
- new RegExp( /^([-+]?\d{1,4})-([01]\d)-([0-3]\d)([T\s]((([01]\d|2[0-3])(:?[0-5]\d)?|24:?00)?(:?([0-5]\d|60))?([.,]\d+)?)([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?/ ),
+ new RegExp( /^([-+]?\d{1,4})-([01]\d)-([0-3]\d)([T\s]((([01]\d|2[0-3])(:?[0-5]\d)?|24:?00)?(:?([0-5]\d|60))?([.,]\d+)?)([zZ]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?/ ),
new RegExp( /^([-+]?\d{1,4})-([01]\d)-([0-3]\d)/ )
],
usLongDate: [
return getParserById( id );
},
- getParsers: function () { // for table diagnosis
+ getParsers: function () { // for table diagnosis
return parsers;
}
};
role: 'navigation',
'aria-labelledby': 'p-lang-label'
} )
- .append( $( '<h3>' ).attr( 'id', 'p-lang-label' ).text( mw.msg( 'otherlanguages' ) ) )
- .append( $( '<div>' ).addClass( 'body' ).append( '<ul>' ) )
+ .append( $( '<h3>' ).attr( 'id', 'p-lang-label' ).text( mw.msg( 'otherlanguages' ) ) )
+ .append( $( '<div>' ).addClass( 'body' ).append( '<ul>' ) )
);
}
return true;
}
- $lis
- .each( function () {
+ $lis.each( function () {
$li = $( this );
$inputs = $li.find( 'input[type="radio"]' );
$oldidRadio = $inputs.filter( '[name="oldid"]' ).eq( 0 );
// Also remove potentially conflicting id attributes that we don't need anyway
$copyForm
.css( 'display', 'none' )
- .find( '[id]' )
- .removeAttr( 'id' )
+ .find( '[id]' ).removeAttr( 'id' )
.end()
.insertAfter( $historyCompareForm )
.submit();
$col = $( '<td colspan="2"></td>' );
$link = $( '<a>' )
- .text( showText )
- .attr( {
- role: 'button',
- tabindex: 0
- } )
- .on( 'click keypress', function ( e ) {
- if (
- e.type === 'click' ||
- e.type === 'keypress' && e.which === 13
- ) {
- if ( $table.hasClass( 'collapsed' ) ) {
- $( this ).text( hideText );
- } else {
- $( this ).text( showText );
+ .text( showText )
+ .attr( {
+ role: 'button',
+ tabindex: 0
+ } )
+ .on( 'click keypress', function ( e ) {
+ if (
+ e.type === 'click' ||
+ e.type === 'keypress' && e.which === 13
+ ) {
+ if ( $table.hasClass( 'collapsed' ) ) {
+ $( this ).text( hideText );
+ } else {
+ $( this ).text( showText );
+ }
+ $table.toggleClass( 'expanded collapsed' );
}
- $table.toggleClass( 'expanded collapsed' );
- }
- } );
+ } );
$col.append( $link );
$row.append( $col );
// Go over the items and define the correct values
$.each( filterRepresentation, function ( name, value ) {
+ // We must store all parameter values as strings '0' or '1'
result[ filterParamNames[ name ] ] = areAnySelected ?
- // We must store all parameter values as strings '0' or '1'
String( Number( !value ) ) :
'0';
} );
$.each( paramRepresentation, function ( paramName, paramValue ) {
var filterItem = paramToFilterMap[ paramName ];
+ // Flip the definition between the parameter
+ // state and the filter state
+ // This is what the 'toggleSelected' value of the filter is
result[ filterItem.getName() ] = areAnySelected ?
- // Flip the definition between the parameter
- // state and the filter state
- // This is what the 'toggleSelected' value of the filter is
!Number( paramValue ) :
// Otherwise, there are no selected items in the
// group, which means the state is false
);
// Translate the parameter values into a filter selection state
this.getItems().forEach( function ( filterItem ) {
+ // All true (either because all values are written or the term 'all' is written)
+ // is the same as all filters set to true
result[ filterItem.getName() ] = (
- // If it is the word 'all'
- paramValues.length === 1 && paramValues[ 0 ] === 'all' ||
- // All values are written
- paramValues.length === model.getItemCount()
- ) ?
- // All true (either because all values are written or the term 'all' is written)
- // is the same as all filters set to true
+ // If it is the word 'all'
+ paramValues.length === 1 && paramValues[ 0 ] === 'all' ||
+ // All values are written
+ paramValues.length === model.getItemCount()
+ ) ?
true :
// Otherwise, the filter is selected only if it appears in the parameter values
paramValues.indexOf( filterItem.getParamName() ) > -1;
} );
};
+ /**
+ * Get all selected items
+ *
+ * @return {mw.rcfilters.dm.FilterItem[]} Selected items
+ */
+ mw.rcfilters.dm.FiltersViewModel.prototype.getSelectedItems = function () {
+ var allSelected = [];
+
+ $.each( this.getFilterGroups(), function ( groupName, groupModel ) {
+ allSelected = allSelected.concat( groupModel.getSelectedItems() );
+ } );
+
+ return allSelected;
+ };
/**
* Switch the current view
*
this.baseFilterState = {};
this.uriProcessor = null;
this.initializing = false;
+
+ this.prevLoggedItems = [];
};
/* Initialization */
this.filtersModel.toggleFilterSelected( filterName, false );
this.updateChangesList();
this.filtersModel.reassessFilterInteractions( filterItem );
+
+ // Log filter grouping
+ this.trackFilterGroupings( 'removefilter' );
}
if ( isHighlighted ) {
this.filtersModel.reassessFilterInteractions();
this.updateChangesList();
+
+ // Log filter grouping
+ this.trackFilterGroupings( 'savedfilters' );
}
};
);
};
+ /**
+ * Track filter grouping usage
+ *
+ * @param {string} action Action taken
+ */
+ mw.rcfilters.Controller.prototype.trackFilterGroupings = function ( action ) {
+ var controller = this,
+ rightNow = new Date().getTime(),
+ randomIdentifier = String( mw.user.sessionId() ) + String( rightNow ) + String( Math.random() ),
+ // Get all current filters
+ filters = this.filtersModel.getSelectedItems().map( function ( item ) {
+ return item.getName();
+ } );
+
+ action = action || 'filtermenu';
+
+ // Check if these filters were the ones we just logged previously
+ // (Don't log the same grouping twice, in case the user opens/closes)
+ // the menu without action, or with the same result
+ if (
+ // Only log if the two arrays are different in size
+ filters.length !== this.prevLoggedItems.length ||
+ // Or if any filters are not the same as the cached filters
+ filters.some( function ( filterName ) {
+ return controller.prevLoggedItems.indexOf( filterName ) === -1;
+ } ) ||
+ // Or if any cached filters are not the same as given filters
+ this.prevLoggedItems.some( function ( filterName ) {
+ return filters.indexOf( filterName ) === -1;
+ } )
+ ) {
+ filters.forEach( function ( filterName ) {
+ mw.track(
+ 'event.ChangesListFilterGrouping',
+ {
+ action: action,
+ groupIdentifier: randomIdentifier,
+ filter: filterName,
+ userId: mw.user.getId()
+ }
+ );
+ } );
+
+ // Cache the filter names
+ this.prevLoggedItems = filters;
+ }
+ };
}( mediaWiki, jQuery ) );
classes: [ 'mw-rcfilters-ui-filterMenuSectionOptionWidget-whatsThisButton' ],
flags: [ 'progressive' ],
popup: {
- $autoCloseIgnore: this.$element.add( this.$overlay ),
padded: false,
align: 'center',
position: 'above',
// Clear the input
this.input.setValue( '' );
}
+
+ // Log filter grouping
+ this.controller.trackFilterGroupings( 'filtermenu' );
}
this.input.setIcon( isVisible ? 'search' : 'menu' );
this.inputValue = '';
this.$overlay = config.$overlay || this.$element;
- this.$body = $( '<div>' )
- .addClass( 'mw-rcfilters-ui-menuSelectWidget-body' );
+ this.$body = $( '<div>' ).addClass( 'mw-rcfilters-ui-menuSelectWidget-body' );
this.footers = [];
// Parent
'aria-labelledby': labelFunc
} );
$fieldsets.not( '#mw-prefsection-personal' )
- .hide()
- .attr( 'aria-hidden', 'true' );
+ .hide()
+ .attr( 'aria-hidden', 'true' );
// T115692: The following is kept for backwards compatibility with older skins
$preferences.addClass( 'jsprefs' );
$tab.attr( {
tabIndex: 0,
'aria-selected': 'true'
- } )
- .focus()
+ } ).focus()
.parent().addClass( 'selected' );
$preferences.children( 'fieldset' ).hide().attr( 'aria-hidden', 'true' );
function detectHash() {
var hash = location.hash,
matchedElement, parentSection;
- if ( hash.match( /^#mw-prefsection-[\w\-]+/ ) ) {
+ if ( hash.match( /^#mw-prefsection-[\w-]+/ ) ) {
mw.storage.session.remove( 'mwpreferences-prevTab' );
switchPrefTab( hash.replace( '#mw-prefsection-', '' ) );
- } else if ( hash.match( /^#mw-[\w\-]+/ ) ) {
+ } else if ( hash.match( /^#mw-[\w-]+/ ) ) {
matchedElement = document.getElementById( hash.slice( 1 ) );
parentSection = $( matchedElement ).closest( '.prefsection' );
if ( parentSection.length ) {
) {
$( window ).on( 'hashchange', function () {
var hash = location.hash;
- if ( hash.match( /^#mw-[\w\-]+/ ) ) {
+ if ( hash.match( /^#mw-[\w-]+/ ) ) {
detectHash();
} else if ( hash === '' ) {
switchPrefTab( 'personal', 'noHash' );
}
} )
- // Run the function immediately to select the proper tab on startup.
- .trigger( 'hashchange' );
+ // Run the function immediately to select the proper tab on startup.
+ .trigger( 'hashchange' );
// In older browsers we'll bind a click handler as fallback.
// We must not have onhashchange *and* the click handlers, otherwise
// the click handler calls switchPrefTab() which sets the hash value,
piprop: 'thumbnail',
pithumbsize: 300,
formatversion: 2
- } )
- .done( function ( resp ) {
+ } ).done( function ( resp ) {
var results = ( resp.query && resp.query.pages ) ? resp.query.pages : false,
multimediaWidgetTemplate;
if ( v.normalize ) {
v = v.normalize();
}
- re = new RegExp( '^\\s*' + v.replace( /([\\{}()|.?*+\-\^$\[\]])/g, '\\$1' ), 'i' );
+ re = new RegExp( '^\\s*' + v.replace( /([\\{}()|.?*+\-^$\[\]])/g, '\\$1' ), 'i' ); // eslint-disable-line no-useless-escape
for ( k in this.values ) {
k = +k;
if ( !isNaN( k ) && re.test( this.values[ k ] ) ) {
var queryValue = this.query.getValue().trim();
if ( queryValue.match( this.externalLinkUrlProtocolsRegExp ) ) {
- queryValue = queryValue.match( /.+\/([^\/]+)/ )[ 1 ];
+ queryValue = queryValue.match( /.+\/([^/]+)/ )[ 1 ];
}
return queryValue;
};
);
currentMonth.add( 1, 'month' );
}
- // Shuffle the array to display months in columns rather than rows.
+ // Shuffle the array to display months in columns rather than rows:
+ // | Jan | Jul |
+ // | Feb | Aug |
+ // | Mar | Sep |
+ // | Apr | Oct |
+ // | May | Nov |
+ // | Jun | Dec |
items = [
- items[ 0 ], items[ 6 ], // | January | July |
- items[ 1 ], items[ 7 ], // | February | August |
- items[ 2 ], items[ 8 ], // | March | September |
- items[ 3 ], items[ 9 ], // | April | October |
- items[ 4 ], items[ 10 ], // | May | November |
- items[ 5 ], items[ 11 ] // | June | December |
+ items[ 0 ], items[ 6 ],
+ items[ 1 ], items[ 7 ],
+ items[ 2 ], items[ 8 ],
+ items[ 3 ], items[ 9 ],
+ items[ 4 ], items[ 10 ],
+ items[ 5 ], items[ 11 ]
];
break;
title: String( page ),
user: user,
uselang: mw.config.get( 'wgUserLanguage' )
- }, params ) )
- .then( function ( data ) {
+ }, params ) ).then( function ( data ) {
return data.rollback;
} );
}
upload = this.uploadWithFormData( file, data );
return upload.then(
- null,
- // If the call fails, we may want to try again...
- retries === 0 ? null : retry,
- function ( fraction ) {
- // Since we're only uploading small parts of a file, we
- // need to adjust the reported progress to reflect where
- // we actually are in the combined upload
- return ( start + fraction * ( end - start ) ) / file.size;
- }
- ).promise( { abort: upload.abort } );
+ null,
+ // If the call fails, we may want to try again...
+ retries === 0 ? null : retry,
+ function ( fraction ) {
+ // Since we're only uploading small parts of a file, we
+ // need to adjust the reported progress to reflect where
+ // we actually are in the combined upload
+ return ( start + fraction * ( end - start ) ) / file.size;
+ }
+ ).promise( { abort: upload.abort } );
},
/**
*/
function hideIfGetField( $el, name ) {
var $found, $p, $widget,
- suffix = name.replace( /^([^\[]+)/, '[$1]' );
+ suffix = name.replace( /^([^[]+)/, '[$1]' );
function nameFilter() {
return this.name === name ||
*/
mw.ForeignStructuredUpload.BookletLayout.prototype.saveFile = function () {
var title = mw.Title.newFromText(
- this.getFilename(),
- mw.config.get( 'wgNamespaceIds' ).file
- );
+ this.getFilename(),
+ mw.config.get( 'wgNamespaceIds' ).file
+ );
return this.uploadPromise
.then( this.validateFilename.bind( this, title ) )
* @return {string} Escaped string
*/
escape: function ( str ) {
- return str.replace( /([\\{}()|.?*+\-\^$\[\]])/g, '\\$1' );
+ return str.replace( /([\\{}()|.?*+\-^$\[\]])/g, '\\$1' ); // eslint-disable-line no-useless-escape
}
};
}( mediaWiki ) );
},
// brackets, greater than
{
- pattern: /[\]\}>]/g,
+ pattern: /[}\]>]/g,
replace: ')',
generalRule: true
},
// brackets, lower than
{
- pattern: /[\[\{<]/g,
+ pattern: /[{[<]/g,
replace: '(',
generalRule: true
},
}
// Any remaining initial :s are illegal.
- title = title.replace( /^\:+/, '' );
+ title = title.replace( /^:+/, '' );
return Title.newFromText( title, namespace );
};
thumbPhpRegex = /thumb\.php/,
regexes = [
// Thumbnails
- /\/[a-f0-9]\/[a-f0-9]{2}\/([^\s\/]+)\/[^\s\/]+-[^\s\/]*$/,
+ /\/[a-f0-9]\/[a-f0-9]{2}\/([^\s/]+)\/[^\s/]+-[^\s/]*$/,
// Full size images
- /\/[a-f0-9]\/[a-f0-9]{2}\/([^\s\/]+)$/,
+ /\/[a-f0-9]\/[a-f0-9]{2}\/([^\s/]+)$/,
// Thumbnails in non-hashed upload directories
- /\/([^\s\/]+)\/[^\s\/]+-(?:\1|thumbnail)[^\s\/]*$/,
+ /\/([^\s/]+)\/[^\s/]+-(?:\1|thumbnail)[^\s/]*$/,
// Full-size images in non-hashed upload directories
- /\/([^\s\/]+)$/
+ /\/([^\s/]+)$/
],
recount = regexes.length;
return $( '<div>' ).prop( {
id: 'mw-debug-' + id,
className: 'mw-debug-bit'
- } )
- .appendTo( $bits );
+ } ).appendTo( $bits );
}
/**
id: 'mw-debug-' + id,
className: 'mw-debug-bit mw-debug-panelink'
} )
- .append( paneLabel( id, text ) )
- .appendTo( $bits );
+ .append( paneLabel( id, text ) )
+ .appendTo( $bits );
}
paneTriggerBitDiv( 'console', 'Console', this.data.log.length );
.append( $( '<th>SQL</th>' ) )
.append( $( '<th>Time</th>' ).css( 'width', '8em' ) )
.append( $( '<th>Call</th>' ).css( 'width', '18em' ) )
- .appendTo( $table );
+ .appendTo( $table );
for ( i = 0, length = this.data.queries.length; i < length; i += 1 ) {
query = this.data.queries[ i ];
.append( $( '<td>' ).text( query.sql ) )
.append( $( '<td class="stats">' ).text( ( query.time * 1000 ).toFixed( 4 ) + 'ms' ) )
.append( $( '<td>' ).text( query[ 'function' ] ) )
- .appendTo( $table );
+ .appendTo( $table );
}
return $table;
*/
mw.Feedback.Dialog.prototype.validateFeedbackForm = function () {
var isValid = (
- (
- !this.useragentMandatory ||
- this.useragentCheckbox.isSelected()
- ) &&
- this.feedbackSubjectInput.getValue()
- );
+ (
+ !this.useragentMandatory ||
+ this.useragentCheckbox.isSelected()
+ ) &&
+ this.feedbackSubjectInput.getValue()
+ );
this.actions.setAbilities( { submit: isValid } );
};
// recursive functions seem not to work in all browsers then. (Tested IE6-7, Opera, Safari, FF)
// This may be because, to save code, memoization was removed
+ /* eslint-disable no-useless-escape */
regularLiteral = makeRegexParser( /^[^{}\[\]$<\\]/ );
regularLiteralWithoutBar = makeRegexParser( /^[^{}\[\]$\\|]/ );
regularLiteralWithoutSpace = makeRegexParser( /^[^{}\[\]$\s]/ );
regularLiteralWithSquareBrackets = makeRegexParser( /^[^{}$\\]/ );
+ /* eslint-enable no-useless-escape */
backslash = makeStringParser( '\\' );
doubleQuote = makeStringParser( '"' );
templateName = transform(
// see $wgLegalTitleChars
// not allowing : due to the need to catch "PLURAL:$1"
- makeRegexParser( /^[ !"$&'()*,.\/0-9;=?@A-Z\^_`a-z~\x80-\xFF+\-]+/ ),
+ makeRegexParser( /^[ !"$&'()*,./0-9;=?@A-Z^_`a-z~\x80-\xFF+-]+/ ),
function ( result ) { return result.toString(); }
);
function templateParam() {
$el.attr( {
role: 'button',
tabindex: 0
- } )
- .on( 'click keypress', function ( e ) {
+ } ).on( 'click keypress', function ( e ) {
if (
e.type === 'click' ||
e.type === 'keypress' && e.which === 13
// Replace the default message parser with jqueryMsg
oldParser = mw.Message.prototype.parser;
mw.Message.prototype.parser = function () {
- if ( this.format === 'plain' || !/\{\{|[\[<>&]/.test( this.map.get( this.key ) ) ) {
+ if ( this.format === 'plain' || !/\{\{|[<>[&]/.test( this.map.get( this.key ) ) ) {
// Fall back to mw.msg's simple parser
return oldParser.apply( this );
}
function setGlobalMapValue( map, key, value ) {
map.values[ key ] = value;
log.deprecate(
- window,
- key,
- value,
- // Deprecation notice for mw.config globals (T58550, T72470)
- map === mw.config && 'Use mw.config instead.'
+ window,
+ key,
+ value,
+ // Deprecation notice for mw.config globals (T58550, T72470)
+ map === mw.config && 'Use mw.config instead.'
);
}
if ( options.tag ) {
// Sanitize options.tag before it is used by any code. (Including Notification class methods)
- options.tag = options.tag.replace( /[ _\-]+/g, '-' ).replace( /[^\-a-z0-9]+/ig, '' );
+ options.tag = options.tag.replace( /[ _-]+/g, '-' ).replace( /[^-a-z0-9]+/ig, '' );
if ( options.tag ) {
$notification.addClass( 'mw-notification-tag-' + options.tag );
} else {
if ( options.type ) {
// Sanitize options.type
- options.type = options.type.replace( /[ _\-]+/g, '-' ).replace( /[^\-a-z0-9]+/ig, '' );
+ options.type = options.type.replace( /[ _-]+/g, '-' ).replace( /[^-a-z0-9]+/ig, '' );
$notification.addClass( 'mw-notification-type-' + options.type );
}
* by that time.
*/
mw.requestIdleCallback = window.requestIdleCallback ?
- // Bind because it throws TypeError if context is not window
- window.requestIdleCallback.bind( window ) :
+ window.requestIdleCallback.bind( window ) : // Bind because it throws TypeError if context is not window
mw.requestIdleCallbackInternal;
// Note: Polyfill was previously disabled due to
// https://bugs.chromium.org/p/chromium/issues/detail?id=647870
*/
function getInputLocation( context ) {
return context.config.$region
- .closest( 'form' )
- .find( '[data-search-loc]' )
- .data( 'search-loc' ) || 'header';
+ .closest( 'form' )
+ .find( '[data-search-loc]' )
+ .data( 'search-loc' ) || 'header';
}
/**
var $this = $( this );
$this
.data( 'suggestions-context' )
- .data.$container
- .css( 'fontSize', $this.css( 'fontSize' ) );
+ .data.$container.css( 'fontSize', $this.css( 'fontSize' ) );
} );
// Ensure that the thing is actually present!
$tocToggleLink
.wrap( '<span class="toctoggle"></span>' )
.parent()
- .prepend( ' [' )
- .append( '] ' )
+ .prepend( ' [' )
+ .append( '] ' )
);
if ( hideToc ) {
formatversion: 2,
action: 'patrol',
rcid: rcid
- } )
- .done( function ( data ) {
+ } ).done( function ( data ) {
var title;
// Remove all patrollinks from the page (including any spinners inside).
$patrolLinks.closest( '.patrollink' ).remove();
// This should never happen as errors should trigger fail
mw.notify( mw.msg( 'markedaspatrollederrornotify' ), { type: 'error' } );
}
- } )
- .fail( function ( error ) {
+ } ).fail( function ( error ) {
$spinner.remove();
// Restore the patrol link. This allows the user to try again
// (or open it in a new window, bypassing this ajax module).
$( e.delegateTarget ).remove();
}, function ( errorCode, data ) {
var message = data && data.error && data.error.messageHtml ?
- $.parseHTML( data.error.messageHtml ) :
- mw.msg( 'rollbackfailed' ),
+ $.parseHTML( data.error.messageHtml ) :
+ mw.msg( 'rollbackfailed' ),
type = errorCode === 'alreadyrolled' ? 'warn' : 'error';
mw.notify( message, {
return $mock;
}
- /** helper to test SpecialRecentchanges::buildMainQueryConds() */
- private function assertConditions(
- $expected,
+ private function buildQuery(
$requestOptions = null,
- $message = '',
$user = null
) {
$context = new RequestContext;
'ChangesListSpecialPageTest::filterOutRcTimestampCondition'
);
+ return $queryConditions;
+ }
+
+ /** helper to test SpecialRecentchanges::buildQuery() */
+ private function assertConditions(
+ $expected,
+ $requestOptions = null,
+ $message = '',
+ $user = null
+ ) {
+ $queryConditions = $this->buildQuery( $requestOptions, $user );
+
$this->assertEquals(
self::normalizeCondition( $expected ),
self::normalizeCondition( $queryConditions ),
);
}
+ public function testFilterUserExpLevelAll() {
+ $this->assertConditions(
+ [
+ # expected
+ ],
+ [
+ 'userExpLevel' => 'registered;unregistered;newcomer;learner;experienced',
+ ],
+ "rc conditions: userExpLevel=registered;unregistered;newcomer;learner;experienced"
+ );
+ }
+
+ public function testFilterUserExpLevelRegisteredUnregistered() {
+ $this->assertConditions(
+ [
+ # expected
+ ],
+ [
+ 'userExpLevel' => 'registered;unregistered',
+ ],
+ "rc conditions: userExpLevel=registered;unregistered"
+ );
+ }
+
+ public function testFilterUserExpLevelRegisteredUnregisteredLearner() {
+ $this->assertConditions(
+ [
+ # expected
+ ],
+ [
+ 'userExpLevel' => 'registered;unregistered;learner',
+ ],
+ "rc conditions: userExpLevel=registered;unregistered;learner"
+ );
+ }
+
+ public function testFilterUserExpLevelAllExperienceLevels() {
+ $this->assertConditions(
+ [
+ # expected
+ 'rc_user != 0',
+ ],
+ [
+ 'userExpLevel' => 'newcomer;learner;experienced',
+ ],
+ "rc conditions: userExpLevel=newcomer;learner;experienced"
+ );
+ }
+
+ public function testFilterUserExpLevelRegistrered() {
+ $this->assertConditions(
+ [
+ # expected
+ 'rc_user != 0',
+ ],
+ [
+ 'userExpLevel' => 'registered',
+ ],
+ "rc conditions: userExpLevel=registered"
+ );
+ }
+
+ public function testFilterUserExpLevelUnregistrered() {
+ $this->assertConditions(
+ [
+ # expected
+ 'rc_user' => 0,
+ ],
+ [
+ 'userExpLevel' => 'unregistered',
+ ],
+ "rc conditions: userExpLevel=unregistered"
+ );
+ }
+
+ public function testFilterUserExpLevelRegistreredOrLearner() {
+ $this->assertConditions(
+ [
+ # expected
+ 'rc_user != 0',
+ ],
+ [
+ 'userExpLevel' => 'registered;learner',
+ ],
+ "rc conditions: userExpLevel=registered;learner"
+ );
+ }
+
+ public function testFilterUserExpLevelUnregistreredOrExperienced() {
+ $conds = $this->buildQuery( [ 'userExpLevel' => 'unregistered;experienced' ] );
+
+ $this->assertRegExp(
+ '/\(rc_user = 0\) OR \(\(user_editcount >= 500\) AND \(user_registration <= \'\d+\'\)\)/',
+ reset( $conds ),
+ "rc conditions: userExpLevel=unregistered;experienced"
+ );
+ }
+
public function testFilterUserExpLevel() {
$now = time();
$this->setMwGlobals( [
$this->fetchUsers( [ 'learner', 'experienced' ], $now ),
'Learner and more experienced'
);
-
- // newcomers, learner, and more experienced
- // TOOD: Fix test. This needs to test that anons are excluded,
- // and right now the join fails.
- /* $this->assertArrayEquals( */
- /* [ */
- /* 'Newcomer1', 'Newcomer2', 'Newcomer3', */
- /* 'Learner1', 'Learner2', 'Learner3', 'Learner4', */
- /* 'Experienced1', */
- /* ], */
- /* $this->fetchUsers( [ 'newcomer', 'learner', 'experienced' ], $now ) */
- /* ); */
}
private function createUsers( $specs, $now ) {
"hideliu" => true,
"userExpLevel" => "newcomer",
],
- "expectedConflicts" => true,
+ "expectedConflicts" => false,
],
[
"parameters" => [
byteLimitTest( {
description: 'Input filter that increases the length',
$input: $( '<input>' ).attr( 'type', 'text' )
- .byteLimit( 10, function ( text ) {
- return 'prefix' + text;
- } ),
+ .byteLimit( 10, function ( text ) {
+ return 'prefix' + text;
+ } ),
sample: simpleSample,
// Prefix adds 6 characters, limit is reached after 4
expected: '1234'
byteLimitTest( {
description: 'Input filter of which the base exceeds the limit',
$input: $( '<input>' ).attr( 'type', 'text' )
- .byteLimit( 3, function ( text ) {
- return 'prefix' + text;
- } ),
+ .byteLimit( 3, function ( text ) {
+ return 'prefix' + text;
+ } ),
sample: simpleSample,
hasLimit: true,
limit: 6, // 'prefix' length
QUnit.test( 'mw-made-collapsible data added', function ( assert ) {
var $collapsible = prepareCollapsible(
- '<div>' + loremIpsum + '</div>'
- );
+ '<div>' + loremIpsum + '</div>'
+ );
assert.equal( $collapsible.data( 'mw-made-collapsible' ), true, 'mw-made-collapsible data present' );
} );
QUnit.test( 'mw-collapsible added when missing', function ( assert ) {
var $collapsible = prepareCollapsible(
- '<div>' + loremIpsum + '</div>'
- );
+ '<div>' + loremIpsum + '</div>'
+ );
assert.assertTrue( $collapsible.hasClass( 'mw-collapsible' ), 'mw-collapsible class present' );
} );
QUnit.test( 'mw-collapsed added when missing', function ( assert ) {
var $collapsible = prepareCollapsible(
'<div>' + loremIpsum + '</div>',
- { collapsed: true }
- );
+ { collapsed: true }
+ );
assert.assertTrue( $collapsible.hasClass( 'mw-collapsed' ), 'mw-collapsed class present' );
} );
'action=options&format=json&formatversion=2&optionname=foo%7Cbar%3Dquux&token=%2B%5C'
] ) !== -1 ) {
assert.ok( true, 'Repond to ' + request.requestBody );
- request.respond( 200, { 'Content-Type': 'application/json' },
- '{ "options": "success" }' );
+ request.respond(
+ 200,
+ { 'Content-Type': 'application/json' },
+ '{ "options": "success" }'
+ );
} else {
assert.ok( false, 'Unexpected request: ' + request.requestBody );
}
'X-Foo': 'Bar'
}
}
- )
- .then( function () {
+ ).then( function () {
assert.equal( test.server.requests[ 0 ].requestHeaders[ 'X-Foo' ], 'Bar', 'Header sent' );
return api.postWithToken( 'csrf',
assert.ok( false, 'This parameter cannot be a callback' );
}
);
- } )
- .then( function ( data ) {
+ } ).then( function ( data ) {
assert.equal( data.example, 'quux' );
assert.equal( test.server.requests.length, 2, 'Request made' );
);
assert.equal(
formatParse( 'external-link-plural', 2, 'http://example.org' ),
- 'Foo <a href=\"http://example.org\">two</a> things.',
+ 'Foo <a href="http://example.org">two</a> things.',
'Link is expanded inside an explicit plural form and is not escaped html'
);
assert.equal(
mw.loader.implement( 'test.promise', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/mwLoaderTestCallback.js' ) ] );
return mw.loader.using( 'test.promise' )
- .done( function () {
- assert.strictEqual( isAwesomeDone, true, 'test.promise module should\'ve caused isAwesomeDone to be true' );
- delete mw.loader.testCallback;
- } )
- .fail( function () {
- assert.ok( false, 'Error callback fired while loader.using "test.promise" module' );
- } );
+ .done( function () {
+ assert.strictEqual( isAwesomeDone, true, 'test.promise module should\'ve caused isAwesomeDone to be true' );
+ delete mw.loader.testCallback;
+ } )
+ .fail( function () {
+ assert.ok( false, 'Error callback fired while loader.using "test.promise" module' );
+ } );
} );
// Covers mw.loader#sortDependencies (with native Set if available)
assert.ok( /Circular/.test( String( e ) ), 'Detect circular dependency' );
}
)
- .always( done );
+ .always( done );
} );
// @covers mw.loader#sortDependencies (with fallback shim)
assert.ok( /Circular/.test( String( e ) ), 'Detect circular dependency' );
}
)
- .always( done );
+ .always( done );
} );
QUnit.test( '.load() - Error: Circular dependency', function ( assert ) {
}
};
} );
- return mw.loader.using( [ 'test.require1', 'test.require2', 'test.require3', 'test.require4' ] )
- .then( function ( require ) {
+ return mw.loader.using( [ 'test.require1', 'test.require2', 'test.require3', 'test.require4' ] ).then( function ( require ) {
var module1, module2, module3, module4;
module1 = require( 'test.require1' );
-/* eslint comma-dangle: 0 */
-/* eslint no-undef: "error" */
-/* eslint no-console: 0 */
/* eslint-env node */
+/* eslint no-undef: "error" */
+/* eslint-disable no-console, comma-dangle */
'use strict';
const path = require( 'path' );
// with "/", then the base url gets prepended.
baseUrl: (
process.env.MW_SERVER === undefined ?
- 'http://127.0.0.1:8080' :
- process.env.MW_SERVER
+ 'http://127.0.0.1:8080' :
+ process.env.MW_SERVER
) + (
process.env.MW_SCRIPT_PATH === undefined ?
- '/w' :
- process.env.MW_SCRIPT_PATH
+ '/w' :
+ process.env.MW_SCRIPT_PATH
),
//
// Default timeout for all waitFor* commands.