* SpecialPage::execute() will now only call checkLoginSecurityLevel() if
getLoginSecurityLevel() returns non-false.
* (T43720, T46197) Improved page display title handling for category pages
+* (T65080) Fixed resetting options of some types via API action=options.
=== Action API changes in 1.32 ===
* Added templated parameters.
*/
$wgUseRCPatrol = true;
-/**
- * Whether a preference is displayed for structured change filters.
- * If false, no preference is displayed and structured change filters are disabled.
- * If true, structured change filters are *enabled* by default, and a preference is displayed
- * that lets users disable them.
- *
- * Temporary variable during development and will be removed.
- *
- * @since 1.30
- */
-$wgStructuredChangeFiltersShowPreference = false;
-
-/**
- * Whether a preference is displayed for structured change filters on watchlist.
- * Works just like $wgStructuredChangeFiltersShowPreference.
- *
- * Temporary variable during development and will be removed
- * @since 1.32
- */
-$wgStructuredChangeFiltersShowWatchlistPreference = false;
-
-/**
- * Whether to enable RCFilters app on Special:Watchlist
- *
- * Temporary variable during development and will be removed.
- */
-$wgStructuredChangeFiltersOnWatchlist = false;
-
/**
* Polling rate, in seconds, used by the 'live update' and 'view newest' features
* of the RCFilters app on SpecialRecentChanges and Special:Watchlist.
* [ 'y' ]
* ]
*
- * @param array $array1,...
+ * @param array ...$args
* @return array
*/
-function wfMergeErrorArrays( /*...*/ ) {
- $args = func_get_args();
+function wfMergeErrorArrays( ...$args ) {
$out = [];
foreach ( $args as $errors ) {
foreach ( $errors as $params ) {
* (https://bugs.php.net/bug.php?id=26285) and the locale problems on Linux in
* PHP 5.2.6+ (bug backported to earlier distro releases of PHP).
*
- * @param string $args,... strings to escape and glue together,
+ * @param string|string[] ...$args strings to escape and glue together,
* or a single array of strings parameter
* @return string
* @deprecated since 1.30 use MediaWiki\Shell::escape()
*/
-function wfEscapeShellArg( /*...*/ ) {
- return Shell::escape( ...func_get_args() );
+function wfEscapeShellArg( ...$args ) {
+ return Shell::escape( ...$args );
}
/**
* Make a cache key for the local wiki.
*
* @deprecated since 1.30 Call makeKey on a BagOStuff instance
- * @param string $args,...
+ * @param string ...$args
* @return string
*/
-function wfMemcKey( /*...*/ ) {
- return ObjectCache::getLocalClusterInstance()->makeKey( ...func_get_args() );
+function wfMemcKey( ...$args ) {
+ return ObjectCache::getLocalClusterInstance()->makeKey( ...$args );
}
/**
*
* @param string $db
* @param string $prefix
- * @param string $args,...
+ * @param string ...$args
* @return string
*/
-function wfForeignMemcKey( $db, $prefix /*...*/ ) {
- $args = array_slice( func_get_args(), 2 );
+function wfForeignMemcKey( $db, $prefix, ...$args ) {
$keyspace = $prefix ? "$db-$prefix" : $db;
return ObjectCache::getLocalClusterInstance()->makeKeyInternal( $keyspace, $args );
}
*
* @deprecated since 1.30 Call makeGlobalKey on a BagOStuff instance
* @since 1.26
- * @param string $args,...
+ * @param string ...$args
* @return string
*/
-function wfGlobalCacheKey( /*...*/ ) {
- return ObjectCache::getLocalClusterInstance()->makeGlobalKey( ...func_get_args() );
+function wfGlobalCacheKey( ...$args ) {
+ return ObjectCache::getLocalClusterInstance()->makeGlobalKey( ...$args );
}
/**
switch ( $prefsKinds[$key] ) {
case 'registered':
// Regular option.
- if ( $htmlForm === null ) {
- // We need a dummy HTMLForm for the validate callback...
- $htmlForm = new HTMLForm( [], $this );
+ if ( $value === null ) {
+ // Reset it
+ $validation = true;
+ } else {
+ // Validate
+ if ( $htmlForm === null ) {
+ // We need a dummy HTMLForm for the validate callback...
+ $htmlForm = new HTMLForm( [], $this );
+ }
+ $field = HTMLForm::loadInputFromParameters( $key, $prefs[$key], $htmlForm );
+ $validation = $field->validate( $value, $user->getOptions() );
}
- $field = HTMLForm::loadInputFromParameters( $key, $prefs[$key], $htmlForm );
- $validation = $field->validate( $value, $user->getOptions() );
break;
case 'registered-multiselect':
case 'registered-checkmatrix':
/**
* @param array $images
*/
- private function invalidateImageDescriptions( $images ) {
+ private function invalidateImageDescriptions( array $images ) {
PurgeJobUtils::invalidatePages( $this->getDB(), NS_FILE, array_keys( $images ) );
}
const LOGS = 8;
const RANGE = 16;
- const BUFFER = 0;
- const STREAM = 1;
-
const TEXT = 0;
const STUB = 1;
- /** @var int */
- public $buffer;
+ const BATCH_SIZE = 1000;
/** @var int */
public $text;
}
/**
- * If using WikiExporter::STREAM to stream a large amount of data,
- * provide a database connection which is not managed by
- * LoadBalancer to read from: some history blob types will
- * make additional queries to pull source data while the
- * main query is still running.
- *
* @param IDatabase $db
* @param int|array $history One of WikiExporter::FULL, WikiExporter::CURRENT,
* WikiExporter::RANGE or WikiExporter::STABLE, or an associative array:
* - offset: non-inclusive offset at which to start the query
* - limit: maximum number of rows to return
* - dir: "asc" or "desc" timestamp order
- * @param int $buffer One of WikiExporter::BUFFER or WikiExporter::STREAM
* @param int $text One of WikiExporter::TEXT or WikiExporter::STUB
*/
- function __construct( $db, $history = self::CURRENT,
- $buffer = self::BUFFER, $text = self::TEXT ) {
+ function __construct( $db, $history = self::CURRENT, $text = self::TEXT ) {
$this->db = $db;
$this->history = $history;
- $this->buffer = $buffer;
$this->writer = new XmlDumpWriter();
$this->sink = new DumpOutput();
$this->text = $text;
* @throws Exception
*/
protected function dumpFrom( $cond = '', $orderRevs = false ) {
- global $wgMultiContentRevisionSchemaMigrationStage;
-
- # For logging dumps...
if ( $this->history & self::LOGS ) {
- $where = [];
- # Hide private logs
- $hideLogs = LogEventsList::getExcludeClause( $this->db );
- if ( $hideLogs ) {
- $where[] = $hideLogs;
- }
- # Add on any caller specified conditions
- if ( $cond ) {
- $where[] = $cond;
- }
- # Get logging table name for logging.* clause
- $logging = $this->db->tableName( 'logging' );
+ $this->dumpLogs( $cond );
+ } else {
+ $this->dumpPages( $cond, $orderRevs );
+ }
+ }
- if ( $this->buffer == self::STREAM ) {
- $prev = $this->db->bufferResults( false );
+ /**
+ * @param string $cond
+ * @throws Exception
+ */
+ protected function dumpLogs( $cond ) {
+ $where = [];
+ # Hide private logs
+ $hideLogs = LogEventsList::getExcludeClause( $this->db );
+ if ( $hideLogs ) {
+ $where[] = $hideLogs;
+ }
+ # Add on any caller specified conditions
+ if ( $cond ) {
+ $where[] = $cond;
+ }
+ # Get logging table name for logging.* clause
+ $logging = $this->db->tableName( 'logging' );
+
+ $result = null; // Assuring $result is not undefined, if exception occurs early
+
+ $commentQuery = CommentStore::getStore()->getJoin( 'log_comment' );
+ $actorQuery = ActorMigration::newMigration()->getJoin( 'log_user' );
+
+ $lastLogId = 0;
+ while ( true ) {
+ $result = $this->db->select(
+ array_merge( [ 'logging' ], $commentQuery['tables'], $actorQuery['tables'], [ 'user' ] ),
+ [ "{$logging}.*", 'user_name' ] + $commentQuery['fields'] + $actorQuery['fields'],
+ array_merge( $where, [ 'log_id > ' . intval( $lastLogId ) ] ),
+ __METHOD__,
+ [
+ 'ORDER BY' => 'log_id',
+ 'USE INDEX' => [ 'logging' => 'PRIMARY' ],
+ 'LIMIT' => self::BATCH_SIZE,
+ ],
+ [
+ 'user' => [ 'JOIN', 'user_id = ' . $actorQuery['fields']['log_user'] ]
+ ] + $commentQuery['joins'] + $actorQuery['joins']
+ );
+
+ if ( !$result->numRows() ) {
+ break;
}
- $result = null; // Assuring $result is not undefined, if exception occurs early
-
- $commentQuery = CommentStore::getStore()->getJoin( 'log_comment' );
- $actorQuery = ActorMigration::newMigration()->getJoin( 'log_user' );
-
- try {
- $result = $this->db->select(
- array_merge( [ 'logging' ], $commentQuery['tables'], $actorQuery['tables'], [ 'user' ] ),
- [ "{$logging}.*", 'user_name' ] + $commentQuery['fields'] + $actorQuery['fields'],
- $where,
- __METHOD__,
- [ 'ORDER BY' => 'log_id', 'USE INDEX' => [ 'logging' => 'PRIMARY' ] ],
- [
- 'user' => [ 'JOIN', 'user_id = ' . $actorQuery['fields']['log_user'] ]
- ] + $commentQuery['joins'] + $actorQuery['joins']
- );
- $this->outputLogStream( $result );
- if ( $this->buffer == self::STREAM ) {
- $this->db->bufferResults( $prev );
- }
- } catch ( Exception $e ) {
- // Throwing the exception does not reliably free the resultset, and
- // would also leave the connection in unbuffered mode.
-
- // Freeing result
- try {
- if ( $result ) {
- $result->free();
- }
- } catch ( Exception $e2 ) {
- // Already in panic mode -> ignoring $e2 as $e has
- // higher priority
- }
- // Putting database back in previous buffer mode
- try {
- if ( $this->buffer == self::STREAM ) {
- $this->db->bufferResults( $prev );
- }
- } catch ( Exception $e2 ) {
- // Already in panic mode -> ignoring $e2 as $e has
- // higher priority
- }
+ $lastLogId = $this->outputLogStream( $result );
+ };
+ }
- // Inform caller about problem
- throw $e;
- }
- # For page dumps...
- } else {
- if ( !( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) ) {
- // TODO: Make XmlDumpWriter use a RevisionStore! (see T198706 and T174031)
- throw new MWException(
- 'Cannot use WikiExporter with SCHEMA_COMPAT_WRITE_OLD mode disabled!'
- . ' Support for dumping from the new schema is not implemented yet!'
- );
- }
+ /**
+ * @param string $cond
+ * @param bool $orderRevs
+ * @throws MWException
+ * @throws Exception
+ */
+ protected function dumpPages( $cond, $orderRevs ) {
+ global $wgMultiContentRevisionSchemaMigrationStage;
+ if ( !( $wgMultiContentRevisionSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) ) {
+ // TODO: Make XmlDumpWriter use a RevisionStore! (see T198706 and T174031)
+ throw new MWException(
+ 'Cannot use WikiExporter with SCHEMA_COMPAT_WRITE_OLD mode disabled!'
+ . ' Support for dumping from the new schema is not implemented yet!'
+ );
+ }
- $revOpts = [ 'page' ];
- if ( $this->text != self::STUB ) {
- // TODO: remove the text and make XmlDumpWriter use a RevisionStore instead! (T198706)
- $revOpts[] = 'text';
- }
- $revQuery = Revision::getQueryInfo( $revOpts );
+ $revOpts = [ 'page' ];
+ if ( $this->text != self::STUB ) {
+ // TODO: remove the text and make XmlDumpWriter use a RevisionStore instead! (T198706)
+ $revOpts[] = 'text';
+ }
+ $revQuery = Revision::getQueryInfo( $revOpts );
- // We want page primary rather than revision
- $tables = array_merge( [ 'page' ], array_diff( $revQuery['tables'], [ 'page' ] ) );
- $join = $revQuery['joins'] + [
+ // We want page primary rather than revision
+ $tables = array_merge( [ 'page' ], array_diff( $revQuery['tables'], [ 'page' ] ) );
+ $join = $revQuery['joins'] + [
'revision' => $revQuery['joins']['page']
];
- unset( $join['page'] );
+ unset( $join['page'] );
- // TODO: remove rev_text_id and make XmlDumpWriter use a RevisionStore instead! (T198706)
- $fields = array_merge( $revQuery['fields'], [ 'page_restrictions, rev_text_id' ] );
+ // TODO: remove rev_text_id and make XmlDumpWriter use a RevisionStore instead! (T198706)
+ $fields = array_merge( $revQuery['fields'], [ 'page_restrictions, rev_text_id' ] );
- $conds = [];
- if ( $cond !== '' ) {
- $conds[] = $cond;
- }
- $opts = [ 'ORDER BY' => 'page_id ASC' ];
- $opts['USE INDEX'] = [];
- if ( is_array( $this->history ) ) {
- # Time offset/limit for all pages/history...
- # Set time order
- if ( $this->history['dir'] == 'asc' ) {
- $op = '>';
- $opts['ORDER BY'] = 'rev_timestamp ASC';
- } else {
- $op = '<';
- $opts['ORDER BY'] = 'rev_timestamp DESC';
- }
- # Set offset
- if ( !empty( $this->history['offset'] ) ) {
- $conds[] = "rev_timestamp $op " .
- $this->db->addQuotes( $this->db->timestamp( $this->history['offset'] ) );
- }
- # Set query limit
- if ( !empty( $this->history['limit'] ) ) {
- $opts['LIMIT'] = intval( $this->history['limit'] );
- }
- } elseif ( $this->history & self::FULL ) {
- # Full history dumps...
- # query optimization for history stub dumps
- if ( $this->text == self::STUB && $orderRevs ) {
- $tables = $revQuery['tables'];
- $opts['ORDER BY'] = [ 'rev_page ASC', 'rev_id ASC' ];
- $opts['USE INDEX']['revision'] = 'rev_page_id';
- unset( $join['revision'] );
- $join['page'] = [ 'INNER JOIN', 'rev_page=page_id' ];
- }
- } elseif ( $this->history & self::CURRENT ) {
- # Latest revision dumps...
- if ( $this->list_authors && $cond != '' ) { // List authors, if so desired
- $this->do_list_authors( $cond );
- }
- $join['revision'] = [ 'INNER JOIN', 'page_id=rev_page AND page_latest=rev_id' ];
- } elseif ( $this->history & self::STABLE ) {
- # "Stable" revision dumps...
- # Default JOIN, to be overridden...
- $join['revision'] = [ 'INNER JOIN', 'page_id=rev_page AND page_latest=rev_id' ];
- # One, and only one hook should set this, and return false
- if ( Hooks::run( 'WikiExporter::dumpStableQuery', [ &$tables, &$opts, &$join ] ) ) {
- throw new MWException( __METHOD__ . " given invalid history dump type." );
- }
- } elseif ( $this->history & self::RANGE ) {
- # Dump of revisions within a specified range
- $opts['ORDER BY'] = [ 'rev_page ASC', 'rev_id ASC' ];
+ $conds = [];
+ if ( $cond !== '' ) {
+ $conds[] = $cond;
+ }
+ $opts = [ 'ORDER BY' => [ 'rev_page ASC', 'rev_id ASC' ] ];
+ $opts['USE INDEX'] = [];
+
+ $op = '>';
+ if ( is_array( $this->history ) ) {
+ # Time offset/limit for all pages/history...
+ # Set time order
+ if ( $this->history['dir'] == 'asc' ) {
+ $opts['ORDER BY'] = 'rev_timestamp ASC';
} else {
- # Unknown history specification parameter?
+ $op = '<';
+ $opts['ORDER BY'] = 'rev_timestamp DESC';
+ }
+ # Set offset
+ if ( !empty( $this->history['offset'] ) ) {
+ $conds[] = "rev_timestamp $op " .
+ $this->db->addQuotes( $this->db->timestamp( $this->history['offset'] ) );
+ }
+ # Set query limit
+ if ( !empty( $this->history['limit'] ) ) {
+ $maxRowCount = intval( $this->history['limit'] );
+ }
+ } elseif ( $this->history & self::FULL ) {
+ # Full history dumps...
+ # query optimization for history stub dumps
+ if ( $this->text == self::STUB && $orderRevs ) {
+ $tables = $revQuery['tables'];
+ $opts['USE INDEX']['revision'] = 'rev_page_id';
+ unset( $join['revision'] );
+ $join['page'] = [ 'INNER JOIN', 'rev_page=page_id' ];
+ }
+ } elseif ( $this->history & self::CURRENT ) {
+ # Latest revision dumps...
+ if ( $this->list_authors && $cond != '' ) { // List authors, if so desired
+ $this->do_list_authors( $cond );
+ }
+ $join['revision'] = [ 'INNER JOIN', 'page_id=rev_page AND page_latest=rev_id' ];
+ } elseif ( $this->history & self::STABLE ) {
+ # "Stable" revision dumps...
+ # Default JOIN, to be overridden...
+ $join['revision'] = [ 'INNER JOIN', 'page_id=rev_page AND page_latest=rev_id' ];
+ # One, and only one hook should set this, and return false
+ if ( Hooks::run( 'WikiExporter::dumpStableQuery', [ &$tables, &$opts, &$join ] ) ) {
throw new MWException( __METHOD__ . " given invalid history dump type." );
}
+ } elseif ( $this->history & self::RANGE ) {
+ # Dump of revisions within a specified range. Condition already set in revsByRange().
+ } else {
+ # Unknown history specification parameter?
+ throw new MWException( __METHOD__ . " given invalid history dump type." );
+ }
- if ( $this->buffer == self::STREAM ) {
- $prev = $this->db->bufferResults( false );
- }
- $result = null; // Assuring $result is not undefined, if exception occurs early
- try {
- Hooks::run( 'ModifyExportQuery',
- [ $this->db, &$tables, &$cond, &$opts, &$join ] );
-
- # Do the query!
- $result = $this->db->select(
- $tables,
- $fields,
- $conds,
- __METHOD__,
- $opts,
- $join
- );
- # Output dump results
- $this->outputPageStream( $result );
-
- if ( $this->buffer == self::STREAM ) {
- $this->db->bufferResults( $prev );
- }
- } catch ( Exception $e ) {
- // Throwing the exception does not reliably free the resultset, and
- // would also leave the connection in unbuffered mode.
-
- // Freeing result
- try {
- if ( $result ) {
- $result->free();
- }
- } catch ( Exception $e2 ) {
- // Already in panic mode -> ignoring $e2 as $e has
- // higher priority
- }
+ $result = null; // Assuring $result is not undefined, if exception occurs early
+ $done = false;
+ $lastRow = null;
+ $revPage = 0;
+ $revId = 0;
+ $rowCount = 0;
- // Putting database back in previous buffer mode
- try {
- if ( $this->buffer == self::STREAM ) {
- $this->db->bufferResults( $prev );
- }
- } catch ( Exception $e2 ) {
- // Already in panic mode -> ignoring $e2 as $e has
- // higher priority
- }
+ $opts['LIMIT'] = self::BATCH_SIZE;
- // Inform caller about problem
- throw $e;
+ Hooks::run( 'ModifyExportQuery',
+ [ $this->db, &$tables, &$cond, &$opts, &$join ] );
+
+ while ( !$done ) {
+ // If necessary, impose the overall maximum and stop looping after this iteration.
+ if ( !empty( $maxRowCount ) && $rowCount + self::BATCH_SIZE > $maxRowCount ) {
+ $opts['LIMIT'] = $maxRowCount - $rowCount;
+ $done = true;
+ }
+
+ $queryConds = $conds;
+ $queryConds[] = 'rev_page>' . intval( $revPage ) . ' OR (rev_page=' .
+ intval( $revPage ) . ' AND rev_id' . $op . intval( $revId ) . ')';
+
+ # Do the query!
+ $result = $this->db->select(
+ $tables,
+ $fields,
+ $queryConds,
+ __METHOD__,
+ $opts,
+ $join
+ );
+ # Output dump results, get new max ids.
+ $lastRow = $this->outputPageStream( $result, $lastRow );
+
+ if ( !$result->numRows() || !$lastRow ) {
+ $done = true;
+ } else {
+ $rowCount += $result->numRows();
+ $revPage = $lastRow->rev_page;
+ $revId = $lastRow->rev_id;
}
}
}
* The result set should be sorted/grouped by page to avoid duplicate
* page records in the output.
*
- * Should be safe for
- * streaming (non-buffered) queries, as long as it was made on a
- * separate database connection not managed by LoadBalancer; some
- * blob storage types will make queries to pull source data.
- *
* @param ResultWrapper $resultset
+ * @param object $lastRow the last row output from the previous call (or null if none)
+ * @return object the last row processed
*/
- protected function outputPageStream( $resultset ) {
- $last = null;
- foreach ( $resultset as $row ) {
- if ( $last === null ||
- $last->page_namespace != $row->page_namespace ||
- $last->page_title != $row->page_title ) {
- if ( $last !== null ) {
- $output = '';
- if ( $this->dumpUploads ) {
- $output .= $this->writer->writeUploads( $last, $this->dumpUploadFileContents );
+ protected function outputPageStream( $resultset, $lastRow ) {
+ if ( $resultset->numRows() ) {
+ foreach ( $resultset as $row ) {
+ if ( $lastRow === null ||
+ $lastRow->page_namespace != $row->page_namespace ||
+ $lastRow->page_title != $row->page_title ) {
+ if ( $lastRow !== null ) {
+ $output = '';
+ if ( $this->dumpUploads ) {
+ $output .= $this->writer->writeUploads( $lastRow, $this->dumpUploadFileContents );
+ }
+ $output .= $this->writer->closePage();
+ $this->sink->writeClosePage( $output );
}
- $output .= $this->writer->closePage();
- $this->sink->writeClosePage( $output );
+ $output = $this->writer->openPage( $row );
+ $this->sink->writeOpenPage( $row, $output );
}
- $output = $this->writer->openPage( $row );
- $this->sink->writeOpenPage( $row, $output );
- $last = $row;
+ $output = $this->writer->writeRevision( $row );
+ $this->sink->writeRevision( $row, $output );
+ $lastRow = $row;
}
- $output = $this->writer->writeRevision( $row );
- $this->sink->writeRevision( $row, $output );
- }
- if ( $last !== null ) {
+ } elseif ( $lastRow !== null ) {
+ // Empty resultset means done with all batches Close off final page element (if any).
$output = '';
if ( $this->dumpUploads ) {
- $output .= $this->writer->writeUploads( $last, $this->dumpUploadFileContents );
+ $output .= $this->writer->writeUploads( $lastRow, $this->dumpUploadFileContents );
}
$output .= $this->author_list;
$output .= $this->writer->closePage();
$this->sink->writeClosePage( $output );
+ $lastRow = null;
}
+
+ return $lastRow;
}
/**
* @param ResultWrapper $resultset
+ * @return int the log_id value of the last item output, or null if none
*/
protected function outputLogStream( $resultset ) {
foreach ( $resultset as $row ) {
$output = $this->writer->writeLogItem( $row );
$this->sink->writeLogItem( $row, $output );
}
+ return isset( $row ) ? $row->log_id : null;
}
}
];
}
- if ( $this->config->get( 'StructuredChangeFiltersShowPreference' ) ) {
- $defaultPreferences['rcenhancedfilters-disable'] = [
- 'type' => 'toggle',
- 'section' => 'rc/optoutrc',
- 'label-message' => 'rcfilters-preference-label',
- 'help-message' => 'rcfilters-preference-help',
- ];
- }
+ $defaultPreferences['rcenhancedfilters-disable'] = [
+ 'type' => 'toggle',
+ 'section' => 'rc/optoutrc',
+ 'label-message' => 'rcfilters-preference-label',
+ 'help-message' => 'rcfilters-preference-help',
+ ];
}
/**
];
}
- if ( $this->config->get( 'StructuredChangeFiltersShowWatchlistPreference' ) ) {
- $defaultPreferences['wlenhancedfilters-disable'] = [
- 'type' => 'toggle',
- 'section' => 'watchlist/optoutwatchlist',
- 'label-message' => 'rcfilters-watchlist-preference-label',
- 'help-message' => 'rcfilters-watchlist-preference-help',
- ];
- }
+ $defaultPreferences['wlenhancedfilters-disable'] = [
+ 'type' => 'toggle',
+ 'section' => 'watchlist/optoutwatchlist',
+ 'label-message' => 'rcfilters-watchlist-preference-label',
+ 'help-message' => 'rcfilters-watchlist-preference-help',
+ ];
}
/**
* @return bool
*/
public function isStructuredFilterUiEnabledByDefault() {
- if ( $this->getConfig()->get( 'StructuredChangeFiltersShowPreference' ) ) {
- return !$this->getUser()->getDefaultOption( 'rcenhancedfilters-disable' );
- } else {
- return $this->getUser()->getDefaultOption( 'rcenhancedfilters' );
- }
+ return true;
}
/**
* @return bool
*/
public static function checkStructuredFilterUiEnabled( Config $config, User $user ) {
- if ( $config->get( 'StructuredChangeFiltersShowPreference' ) ) {
- return !$user->getOption( 'rcenhancedfilters-disable' );
- } else {
- return $user->getOption( 'rcenhancedfilters' );
- }
+ return !$user->getOption( 'rcenhancedfilters-disable' );
}
/**
* @ingroup SpecialPage
*/
-use MediaWiki\MediaWikiServices;
use MediaWiki\Logger\LoggerFactory;
/**
}
/* Ok, let's get to it... */
- if ( $history == WikiExporter::CURRENT ) {
- $lb = false;
- $db = wfGetDB( DB_REPLICA );
- $buffer = WikiExporter::BUFFER;
- } else {
- // Use an unbuffered query; histories may be very long!
- $lb = MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->newMainLB();
- $db = $lb->getConnection( DB_REPLICA );
- $buffer = WikiExporter::STREAM;
-
- // This might take a while... :D
- Wikimedia\suppressWarnings();
- set_time_limit( 0 );
- Wikimedia\restoreWarnings();
- }
+ $lb = false;
+ $db = wfGetDB( DB_REPLICA );
- $exporter = new WikiExporter( $db, $history, $buffer );
+ $exporter = new WikiExporter( $db, $history );
$exporter->list_authors = $list_authors;
$exporter->openStream();
}
public static function checkStructuredFilterUiEnabled( Config $config, User $user ) {
- if ( !$config->get( 'StructuredChangeFiltersOnWatchlist' ) ) {
- return false;
- }
-
- if ( $config->get( 'StructuredChangeFiltersShowWatchlistPreference' ) ) {
- return !$user->getOption( 'wlenhancedfilters-disable' );
- } else {
- return $user->getOption( 'rcenhancedfilters' );
- }
+ return !$user->getOption( 'wlenhancedfilters-disable' );
}
/**
// For some reason rev_parent_id isn't populated for this row.
// Its rumoured this is true on wikipedia for some revisions (T36922).
// Next best thing is to have the total number of bytes.
- $chardiff = ' <span class="mw-changeslist-separator">. .</span> ';
+ $chardiff = ' <span class="mw-changeslist-separator"></span> ';
$chardiff .= Linker::formatRevisionSize( $row->rev_len );
- $chardiff .= ' <span class="mw-changeslist-separator">. .</span> ';
+ $chardiff .= ' <span class="mw-changeslist-separator"></span> ';
} else {
$parentLen = 0;
if ( isset( $this->mParentLens[$row->rev_parent_id] ) ) {
$parentLen = $this->mParentLens[$row->rev_parent_id];
}
- $chardiff = ' <span class="mw-changeslist-separator">. .</span> ';
+ $chardiff = ' <span class="mw-changeslist-separator"></span> ';
$chardiff .= ChangesList::showCharacterDifference(
$parentLen,
$row->rev_len,
$this->getContext()
);
- $chardiff .= ' <span class="mw-changeslist-separator">. .</span> ';
+ $chardiff .= ' <span class="mw-changeslist-separator"></span> ';
}
$lang = $this->getLanguage();
$userlink = '';
if ( ( $this->contribs == 'newbie' && !$rev->isDeleted( Revision::DELETED_USER ) )
|| $this->isQueryableRange( $this->target ) ) {
- $userlink = ' . . ' . $lang->getDirMark()
+ $userlink = ' <span class="mw-changeslist-separator"></span> '
+ . $lang->getDirMark()
. Linker::userLink( $rev->getUser(), $rev->getUserText() );
$userlink .= ' ' . $this->msg( 'parentheses' )->rawParams(
Linker::userTalkLink( $rev->getUser(), $rev->getUserText() ) )->escaped() . ' ';
* @return bool
*/
public function setPassword( $str ) {
+ wfDeprecated( __METHOD__, '1.27' );
return $this->setPasswordInternal( $str );
}
* through the web interface.
*/
public function setInternalPassword( $str ) {
+ wfDeprecated( __METHOD__, '1.27' );
$this->setPasswordInternal( $str );
}
* @return bool True if the given password is correct, otherwise False
*/
public function checkPassword( $password ) {
+ wfDeprecated( __METHOD__, '1.27' );
+
$manager = AuthManager::singleton();
$reqs = AuthenticationRequest::loadRequestsFromSubmission(
$manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ),
* @return bool True if matches, false otherwise
*/
public function checkTemporaryPassword( $plaintext ) {
+ wfDeprecated( __METHOD__, '1.27' );
// Can't check the temporary password individually.
return $this->checkPassword( $plaintext );
}
$this->initProgress( $history );
$db = $this->backupDb();
- $exporter = new WikiExporter( $db, $history, WikiExporter::STREAM, $text );
+ $exporter = new WikiExporter( $db, $history, $text );
$exporter->dumpUploads = $this->dumpUploads;
$exporter->dumpUploadFileContents = $this->dumpUploadFileContents;
.mw-rcfilters-ui-highlights {
display: none;
}
+
+/* Content dividers */
+/* @todo FIXME: Hard coded ". .". Is there a message for this? Should there be? */
+.mw-changeslist-separator:empty:before {
+ content: '. .';
+}
};
/**
- * Category selector capsule item widget. Extends OO.ui.CapsuleItemWidget with the ability to link
+ * Category selector tag item widget. Extends OO.ui.TagItemWidget with the ability to link
* to the given page, and to show its existence status (i.e., whether it is a redlink).
*
* @class mw.widgets.CategoryTagItemWidget
.addClass( 'new' );
}
};
-
- // For backwards compatibility. See T183299.
- mw.widgets.CategoryCapsuleItemWidget = mw.widgets.CategoryTagItemWidget;
}() );
[ $this, 'hookGetPreferences' ]
]
] );
+ $this->mergeMwGlobalArrayValue( 'wgDefaultUserOptions', [
+ 'testradio' => 'option1',
+ ] );
+ // Workaround for static caching in User::getDefaultOptions()
+ $this->setContentLang( Language::factory( 'qqq' ) );
}
public function hookGetPreferences( $user, &$preferences ) {
'default' => [],
];
- return true;
+ $preferences['testradio'] = [
+ 'type' => 'radio',
+ 'options' => [ 'Option 1' => 'option1', 'Option 2' => 'option2' ],
+ 'section' => 'test',
+ ];
}
/**
'willBeNull' => 'registered',
'willBeEmpty' => 'registered',
'willBeHappy' => 'registered',
+ 'testradio' => 'registered',
'testmultiselect-opt1' => 'registered-multiselect',
'testmultiselect-opt2' => 'registered-multiselect',
'testmultiselect-opt3' => 'registered-multiselect',
$this->assertEquals( self::$Success, $response );
}
- public function testOptionWithValue() {
- $this->mUserMock->expects( $this->never() )
- ->method( 'resetOptions' );
-
- $this->mUserMock->expects( $this->once() )
- ->method( 'setOption' )
- ->with( $this->equalTo( 'name' ), $this->equalTo( 'value' ) );
-
- $this->mUserMock->expects( $this->once() )
- ->method( 'saveSettings' );
-
- $request = $this->getSampleRequest( [ 'optionname' => 'name', 'optionvalue' => 'value' ] );
-
- $response = $this->executeQuery( $request );
-
- $this->assertEquals( self::$Success, $response );
- }
-
- public function testOptionResetValue() {
- $this->mUserMock->expects( $this->never() )
- ->method( 'resetOptions' );
-
- $this->mUserMock->expects( $this->once() )
- ->method( 'setOption' )
- ->with( $this->equalTo( 'name' ), $this->identicalTo( null ) );
-
- $this->mUserMock->expects( $this->once() )
- ->method( 'saveSettings' );
-
- $request = $this->getSampleRequest( [ 'optionname' => 'name' ] );
- $response = $this->executeQuery( $request );
-
- $this->assertEquals( self::$Success, $response );
- }
-
- public function testChange() {
- $this->mUserMock->expects( $this->never() )
- ->method( 'resetOptions' );
-
- $this->mUserMock->expects( $this->exactly( 3 ) )
- ->method( 'setOption' )
- ->withConsecutive(
- [ $this->equalTo( 'willBeNull' ), $this->identicalTo( null ) ],
- [ $this->equalTo( 'willBeEmpty' ), $this->equalTo( '' ) ],
- [ $this->equalTo( 'willBeHappy' ), $this->equalTo( 'Happy' ) ]
- );
-
- $this->mUserMock->expects( $this->once() )
- ->method( 'saveSettings' );
-
- $request = $this->getSampleRequest( [
- 'change' => 'willBeNull|willBeEmpty=|willBeHappy=Happy'
- ] );
-
- $response = $this->executeQuery( $request );
-
- $this->assertEquals( self::$Success, $response );
- }
-
public function testResetChangeOption() {
$this->mUserMock->expects( $this->once() )
->method( 'resetOptions' );
$this->assertEquals( self::$Success, $response );
}
- public function testMultiSelect() {
+ /**
+ * @dataProvider provideOptionManupulation
+ * @param array $params
+ * @param array $setOptions
+ * @param array|null $result
+ */
+ public function testOptionManupulation( array $params, array $setOptions, array $result = null,
+ $message = ''
+ ) {
$this->mUserMock->expects( $this->never() )
->method( 'resetOptions' );
- $this->mUserMock->expects( $this->exactly( 4 ) )
+ $this->mUserMock->expects( $this->exactly( count( $setOptions ) ) )
->method( 'setOption' )
- ->withConsecutive(
- [ $this->equalTo( 'testmultiselect-opt1' ), $this->identicalTo( true ) ],
- [ $this->equalTo( 'testmultiselect-opt2' ), $this->identicalTo( null ) ],
- [ $this->equalTo( 'testmultiselect-opt3' ), $this->identicalTo( false ) ],
- [ $this->equalTo( 'testmultiselect-opt4' ), $this->identicalTo( false ) ]
- );
-
- $this->mUserMock->expects( $this->once() )
- ->method( 'saveSettings' );
-
- $request = $this->getSampleRequest( [
- 'change' => 'testmultiselect-opt1=1|testmultiselect-opt2|'
- . 'testmultiselect-opt3=|testmultiselect-opt4=0'
- ] );
-
- $response = $this->executeQuery( $request );
-
- $this->assertEquals( self::$Success, $response );
- }
-
- public function testSpecialOption() {
- $this->mUserMock->expects( $this->never() )
- ->method( 'resetOptions' );
-
- $this->mUserMock->expects( $this->never() )
- ->method( 'saveSettings' );
-
- $request = $this->getSampleRequest( [
- 'change' => 'special=1'
- ] );
-
- $response = $this->executeQuery( $request );
-
- $this->assertEquals( [
- 'options' => 'success',
- 'warnings' => [
- 'options' => [
- 'warnings' => "Validation error for \"special\": cannot be set by this module."
- ]
- ]
- ], $response );
- }
-
- public function testUnknownOption() {
- $this->mUserMock->expects( $this->never() )
- ->method( 'resetOptions' );
-
- $this->mUserMock->expects( $this->never() )
- ->method( 'saveSettings' );
-
- $request = $this->getSampleRequest( [
- 'change' => 'unknownOption=1'
- ] );
+ ->withConsecutive( ...$setOptions );
+
+ if ( $setOptions ) {
+ $this->mUserMock->expects( $this->once() )
+ ->method( 'saveSettings' );
+ } else {
+ $this->mUserMock->expects( $this->never() )
+ ->method( 'saveSettings' );
+ }
+ $request = $this->getSampleRequest( $params );
$response = $this->executeQuery( $request );
- $this->assertEquals( [
- 'options' => 'success',
- 'warnings' => [
- 'options' => [
- 'warnings' => "Validation error for \"unknownOption\": not a valid preference."
- ]
- ]
- ], $response );
+ if ( !$result ) {
+ $result = self::$Success;
+ }
+ $this->assertEquals( $result, $response, $message );
}
- public function testUserjsOption() {
- $this->mUserMock->expects( $this->never() )
- ->method( 'resetOptions' );
-
- $this->mUserMock->expects( $this->once() )
- ->method( 'setOption' )
- ->with( $this->equalTo( 'userjs-option' ), $this->equalTo( '1' ) );
-
- $this->mUserMock->expects( $this->once() )
- ->method( 'saveSettings' );
-
- $request = $this->getSampleRequest( [
- 'change' => 'userjs-option=1'
- ] );
-
- $response = $this->executeQuery( $request );
-
- $this->assertEquals( self::$Success, $response );
+ public function provideOptionManupulation() {
+ return [
+ [
+ [ 'change' => 'userjs-option=1' ],
+ [ [ 'userjs-option', '1' ] ],
+ null,
+ 'Setting userjs options',
+ ],
+ [
+ [ 'change' => 'willBeNull|willBeEmpty=|willBeHappy=Happy' ],
+ [
+ [ 'willBeNull', null ],
+ [ 'willBeEmpty', '' ],
+ [ 'willBeHappy', 'Happy' ],
+ ],
+ null,
+ 'Basic option setting',
+ ],
+ [
+ [ 'change' => 'testradio=option2' ],
+ [ [ 'testradio', 'option2' ] ],
+ null,
+ 'Changing radio options',
+ ],
+ [
+ [ 'change' => 'testradio' ],
+ [ [ 'testradio', null ] ],
+ null,
+ 'Resetting radio options',
+ ],
+ [
+ [ 'change' => 'unknownOption=1' ],
+ [],
+ [
+ 'options' => 'success',
+ 'warnings' => [
+ 'options' => [
+ 'warnings' => "Validation error for \"unknownOption\": not a valid preference."
+ ],
+ ],
+ ],
+ 'Unrecognized options should be rejected',
+ ],
+ [
+ [ 'change' => 'special=1' ],
+ [],
+ [
+ 'options' => 'success',
+ 'warnings' => [
+ 'options' => [
+ 'warnings' => "Validation error for \"special\": cannot be set by this module."
+ ]
+ ]
+ ],
+ 'Refuse setting special options',
+ ],
+ [
+ [
+ 'change' => 'testmultiselect-opt1=1|testmultiselect-opt2|'
+ . 'testmultiselect-opt3=|testmultiselect-opt4=0'
+ ],
+ [
+ [ 'testmultiselect-opt1', true ],
+ [ 'testmultiselect-opt2', null ],
+ [ 'testmultiselect-opt3', false ],
+ [ 'testmultiselect-opt4', false ],
+ ],
+ null,
+ 'Setting multiselect options',
+ ],
+ [
+ [ 'optionname' => 'name', 'optionvalue' => 'value' ],
+ [ [ 'name', 'value' ] ],
+ null,
+ 'Setting options via optionname/optionvalue'
+ ],
+ [
+ [ 'optionname' => 'name' ],
+ [ [ 'name', null ] ],
+ null,
+ 'Resetting options via optionname without optionvalue',
+ ],
+ ];
}
}
/**
* @dataProvider validateOptionsProvider
*/
- public function testValidateOptions( $optionsToSet, $expectedRedirect, $expectedRedirectOptions ) {
+ public function testValidateOptions(
+ $optionsToSet,
+ $expectedRedirect,
+ $expectedRedirectOptions,
+ $rcfilters
+ ) {
$redirectQuery = [];
$redirected = false;
$output = $this->getMockBuilder( OutputPage::class )
// Give users patrol permissions so we can test that.
$user = $this->getTestSysop()->getUser();
+ $user->setOption( 'rcenhancedfilters-disable', $rcfilters ? 0 : 1 );
$ctx->setUser( $user );
// Disable this hook or it could break changeType
* @covers ChangesListSpecialPage
*/
class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase {
- public function setUp() {
- parent::setUp();
- $this->setMwGlobals( [
- 'wgStructuredChangeFiltersShowPreference' => true,
- ] );
- }
-
protected function getPage() {
$mock = $this->getMockBuilder( ChangesListSpecialPage::class )
->setConstructorArgs(
[ 'hideanons' => 1, 'hideliu' => 1, 'hidebots' => 1 ],
true,
[ 'userExpLevel' => 'unregistered', 'hidebots' => 1, ],
+ true,
],
[
[ 'hideanons' => 1, 'hideliu' => 1, 'hidebots' => 0 ],
true,
[ 'hidebots' => 0, 'hidehumans' => 1 ],
+ true,
],
[
[ 'hideanons' => 1 ],
true,
- [ 'userExpLevel' => 'registered' ]
+ [ 'userExpLevel' => 'registered' ],
+ true,
],
[
[ 'hideliu' => 1 ],
true,
- [ 'userExpLevel' => 'unregistered' ]
+ [ 'userExpLevel' => 'unregistered' ],
+ true,
],
[
[ 'hideanons' => 1, 'hidebots' => 1 ],
true,
- [ 'userExpLevel' => 'registered', 'hidebots' => 1 ]
+ [ 'userExpLevel' => 'registered', 'hidebots' => 1 ],
+ true,
],
[
[ 'hideliu' => 1, 'hidebots' => 0 ],
true,
- [ 'userExpLevel' => 'unregistered', 'hidebots' => 0 ]
+ [ 'userExpLevel' => 'unregistered', 'hidebots' => 0 ],
+ true,
],
[
[ 'hidemyself' => 1, 'hidebyothers' => 1 ],
true,
[],
+ true,
],
[
[ 'hidebots' => 1, 'hidehumans' => 1 ],
true,
[],
+ true,
],
[
[ 'hidepatrolled' => 1, 'hideunpatrolled' => 1 ],
true,
[],
+ true,
],
[
[ 'hideminor' => 1, 'hidemajor' => 1 ],
true,
[],
+ true,
],
[
// changeType
[ 'hidepageedits' => 1, 'hidenewpages' => 1, 'hidecategorization' => 1, 'hidelog' => 1, ],
true,
[],
+ true,
],
];
}
[ 'hideanons' => 1, 'hideliu' => 1 ],
true,
[ 'hideliu' => 1 ],
+ false,
],
];
}