Merge "rdbms: make * consistently act like in select/insertSelect methods"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 2 Oct 2018 16:44:54 +0000 (16:44 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 2 Oct 2018 16:44:54 +0000 (16:44 +0000)
23 files changed:
RELEASE-NOTES-1.32
includes/DefaultSettings.php
includes/GlobalFunctions.php
includes/api/ApiOptions.php
includes/deferred/LinksUpdate.php
includes/export/WikiExporter.php
includes/page/Article.php
includes/poolcounter/PoolWorkArticleView.php
includes/preferences/DefaultPreferencesFactory.php
includes/specialpage/ChangesListSpecialPage.php
includes/specials/SpecialExport.php
includes/specials/SpecialWatchlist.php
includes/specials/pagers/ContribsPager.php
includes/user/User.php
maintenance/includes/BackupDumper.php
resources/src/mediawiki.special.changeslist.css
resources/src/mediawiki.widgets/mw.widgets.CategoryTagItemWidget.js
tests/phpunit/includes/api/ApiOptionsTest.php
tests/phpunit/includes/page/ArticleViewTest.php
tests/phpunit/includes/poolcounter/PoolWorkArticleViewTest.php
tests/phpunit/includes/specialpage/AbstractChangesListSpecialPageTestCase.php
tests/phpunit/includes/specialpage/ChangesListSpecialPageTest.php
tests/phpunit/includes/specials/SpecialRecentchangesTest.php

index d875017..6c200d9 100644 (file)
@@ -122,6 +122,7 @@ production.
 * 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.
index 96d2e22..9ea1a25 100644 (file)
@@ -6929,34 +6929,6 @@ $wgRCWatchCategoryMembership = false;
  */
 $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.
index c769b0a..868fda3 100644 (file)
@@ -197,11 +197,10 @@ function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
  *       [ '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 ) {
@@ -2176,13 +2175,13 @@ function wfStringToBool( $val ) {
  * (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 );
 }
 
 /**
@@ -2625,11 +2624,11 @@ function wfGetPrecompiledData( $name ) {
  * 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 );
 }
 
 /**
@@ -2639,11 +2638,10 @@ function wfMemcKey( /*...*/ ) {
  *
  * @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 );
 }
@@ -2657,11 +2655,11 @@ function wfForeignMemcKey( $db, $prefix /*...*/ ) {
  *
  * @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 );
 }
 
 /**
index fe7d10d..3ea827c 100644 (file)
@@ -80,12 +80,18 @@ class ApiOptions extends ApiBase {
                        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':
index dbe387b..577a272 100644 (file)
@@ -405,7 +405,7 @@ class LinksUpdate extends DataUpdate implements EnqueueableDataUpdate {
        /**
         * @param array $images
         */
-       private function invalidateImageDescriptions( $images ) {
+       private function invalidateImageDescriptions( array $images ) {
                PurgeJobUtils::invalidatePages( $this->getDB(), NS_FILE, array_keys( $images ) );
        }
 
index b018584..1f2b81d 100644 (file)
@@ -52,14 +52,10 @@ class WikiExporter {
        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;
@@ -76,26 +72,17 @@ class WikiExporter {
        }
 
        /**
-        * 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;
@@ -263,206 +250,191 @@ class WikiExporter {
         * @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;
                        }
                }
        }
@@ -472,52 +444,55 @@ class WikiExporter {
         * 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;
        }
 }
index b6f5dce..4a689d3 100644 (file)
@@ -767,7 +767,9 @@ class Article implements Page {
                                                        $parserOptions,
                                                        $this->getRevIdFetched(),
                                                        $useParserCache,
-                                                       $rev
+                                                       $rev,
+                                                       // permission checking was done earlier via showDeletedRevisionHeader()
+                                                       RevisionRecord::RAW
                                                );
                                                $ok = $poolArticleView->execute();
                                                $error = $poolArticleView->getError();
index 157b508..6e6a574 100644 (file)
@@ -44,6 +44,9 @@ class PoolWorkArticleView extends PoolCounterWork {
        /** @var RevisionRecord|null */
        private $revision = null;
 
+       /** @var int */
+       private $audience;
+
        /** @var RevisionStore */
        private $revisionStore = null;
 
@@ -67,9 +70,10 @@ class PoolWorkArticleView extends PoolCounterWork {
         *   operation.
         * @param RevisionRecord|Content|string|null $revision Revision to render, or null to load it;
         *        may also be given as a wikitext string, or a Content object, for BC.
+        * @param int $audience One of the RevisionRecord audience constants
         */
        public function __construct( WikiPage $page, ParserOptions $parserOptions,
-               $revid, $useParserCache, $revision = null
+               $revid, $useParserCache, $revision = null, $audience = RevisionRecord::FOR_PUBLIC
        ) {
                if ( is_string( $revision ) ) { // BC: very old style call
                        $modelId = $page->getRevision()->getContentModel();
@@ -108,6 +112,7 @@ class PoolWorkArticleView extends PoolCounterWork {
                $this->cacheable = $useParserCache;
                $this->parserOptions = $parserOptions;
                $this->revision = $revision;
+               $this->audience = $audience;
                $this->cacheKey = $this->parserCache->getKey( $page, $parserOptions );
                $keyPrefix = $this->cacheKey ?: wfMemcKey( 'articleview', 'missingcachekey' );
 
@@ -152,8 +157,8 @@ class PoolWorkArticleView extends PoolCounterWork {
 
                $isCurrent = $this->revid === $this->page->getLatest();
 
-               // Bypass audience check for current revision
-               $audience = $isCurrent ? RevisionRecord::RAW : RevisionRecord::FOR_PUBLIC;
+               // The current revision cannot be hidden so we can skip some checks.
+               $audience = $isCurrent ? RevisionRecord::RAW : $this->audience;
 
                if ( $this->revision !== null ) {
                        $rev = $this->revision;
index 555493a..880da60 100644 (file)
@@ -1060,14 +1060,12 @@ class DefaultPreferencesFactory implements PreferencesFactory {
                        ];
                }
 
-               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',
+               ];
        }
 
        /**
@@ -1264,14 +1262,12 @@ class DefaultPreferencesFactory implements PreferencesFactory {
                        ];
                }
 
-               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',
+               ];
        }
 
        /**
index 43b4e98..36d3eef 100644 (file)
@@ -1846,11 +1846,7 @@ abstract class ChangesListSpecialPage extends SpecialPage {
         * @return bool
         */
        public function isStructuredFilterUiEnabledByDefault() {
-               if ( $this->getConfig()->get( 'StructuredChangeFiltersShowPreference' ) ) {
-                       return !$this->getUser()->getDefaultOption( 'rcenhancedfilters-disable' );
-               } else {
-                       return $this->getUser()->getDefaultOption( 'rcenhancedfilters' );
-               }
+               return true;
        }
 
        /**
@@ -1862,11 +1858,7 @@ abstract class ChangesListSpecialPage extends SpecialPage {
         * @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' );
        }
 
        /**
index 3a7e9cd..513e7a9 100644 (file)
@@ -23,7 +23,6 @@
  * @ingroup SpecialPage
  */
 
-use MediaWiki\MediaWikiServices;
 use MediaWiki\Logger\LoggerFactory;
 
 /**
@@ -379,23 +378,10 @@ class SpecialExport extends SpecialPage {
                }
 
                /* 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();
 
index 432cfcc..2445c10 100644 (file)
@@ -111,15 +111,7 @@ class SpecialWatchlist extends ChangesListSpecialPage {
        }
 
        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' );
        }
 
        /**
index 81a1f5a..5b50f0a 100644 (file)
@@ -502,22 +502,22 @@ class ContribsPager extends RangeChronologicalPager {
                                // 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();
@@ -543,7 +543,8 @@ class ContribsPager extends RangeChronologicalPager {
                        $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() . ' ';
index a29fbf8..12623e8 100644 (file)
@@ -2876,6 +2876,7 @@ class User implements IDBAccessObject, UserIdentity {
         * @return bool
         */
        public function setPassword( $str ) {
+               wfDeprecated( __METHOD__, '1.27' );
                return $this->setPasswordInternal( $str );
        }
 
@@ -2888,6 +2889,7 @@ class User implements IDBAccessObject, UserIdentity {
         *  through the web interface.
         */
        public function setInternalPassword( $str ) {
+               wfDeprecated( __METHOD__, '1.27' );
                $this->setPasswordInternal( $str );
        }
 
@@ -4546,6 +4548,8 @@ class User implements IDBAccessObject, UserIdentity {
         * @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 ),
@@ -4579,6 +4583,7 @@ class User implements IDBAccessObject, UserIdentity {
         * @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 );
        }
index e8993e4..4c2b64c 100644 (file)
@@ -257,7 +257,7 @@ abstract class BackupDumper extends Maintenance {
                $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;
 
index 1b37ec3..a884b83 100644 (file)
@@ -60,3 +60,9 @@
 .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: '. .';
+}
index 819bc23..a15337c 100644 (file)
        };
 
        /**
-        * 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;
 }() );
index 29c7dae..30ba1c1 100644 (file)
@@ -61,6 +61,11 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
                                [ $this, 'hookGetPreferences' ]
                        ]
                ] );
+               $this->mergeMwGlobalArrayValue( 'wgDefaultUserOptions', [
+                       'testradio' => 'option1',
+               ] );
+               // Workaround for static caching in User::getDefaultOptions()
+               $this->setContentLang( Language::factory( 'qqq' ) );
        }
 
        public function hookGetPreferences( $user, &$preferences ) {
@@ -90,7 +95,11 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
                        'default' => [],
                ];
 
-               return true;
+               $preferences['testradio'] = [
+                       'type' => 'radio',
+                       'options' => [ 'Option 1' => 'option1', 'Option 2' => 'option2' ],
+                       'section' => 'test',
+               ];
        }
 
        /**
@@ -106,6 +115,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
                        'willBeNull' => 'registered',
                        'willBeEmpty' => 'registered',
                        'willBeHappy' => 'registered',
+                       'testradio' => 'registered',
                        'testmultiselect-opt1' => 'registered-multiselect',
                        'testmultiselect-opt2' => 'registered-multiselect',
                        'testmultiselect-opt3' => 'registered-multiselect',
@@ -243,65 +253,6 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
                $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' );
@@ -328,95 +279,121 @@ class ApiOptionsTest extends MediaWikiLangTestCase {
                $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',
+                       ],
+               ];
        }
 }
index d07a9e1..629621e 100644 (file)
@@ -298,6 +298,34 @@ class ArticleViewTest extends MediaWikiTestCase {
                $this->assertNotContains( 'Test B', $this->getHtml( $output ) );
        }
 
+       public function testUnhiddenViewOfDeletedRevision() {
+               $revisions = [];
+               $page = $this->getPage( __METHOD__, [ 1 => 'Test A', 2 => 'Test B' ], $revisions );
+               $idA = $revisions[1]->getId();
+
+               $revDelList = new RevDelRevisionList(
+                       RequestContext::getMain(), $page->getTitle(), [ $idA ]
+               );
+               $revDelList->setVisibility( [
+                       'value' => [ RevisionRecord::DELETED_TEXT => 1 ],
+                       'comment' => "Testing",
+               ] );
+
+               $article = new Article( $page->getTitle(), $idA );
+               $context = new DerivativeContext( $article->getContext() );
+               $article->setContext( $context );
+               $context->getOutput()->setTitle( $page->getTitle() );
+               $context->getRequest()->setVal( 'unhide', 1 );
+               $context->setUser( $this->getTestUser( [ 'sysop' ] )->getUser() );
+               $article->view();
+
+               $output = $article->getContext()->getOutput();
+               $this->assertContains( '(rev-deleted-text-view)', $this->getHtml( $output ) );
+
+               $this->assertContains( 'Test A', $this->getHtml( $output ) );
+               $this->assertNotContains( 'Test B', $this->getHtml( $output ) );
+       }
+
        public function testViewMissingPage() {
                $page = $this->getPage( __METHOD__ );
 
index a0beb45..47adfc0 100644 (file)
@@ -164,6 +164,10 @@ class PoolWorkArticleViewTest extends MediaWikiTestCase {
                $work = new PoolWorkArticleView( $page, $options, $rev1->getId(), false, $fakeRev );
                $this->assertFalse( $work->execute() );
 
+               $work = new PoolWorkArticleView( $page, $options, $rev1->getId(), false, $fakeRev,
+                       RevisionRecord::RAW );
+               $this->assertNotFalse( $work->execute() );
+
                // a deleted current revision should still be show
                $fakeRev->setId( $rev2->getId() );
                $work = new PoolWorkArticleView( $page, $options, $rev2->getId(), false, $fakeRev );
index d84fcd7..d57d489 100644 (file)
@@ -91,7 +91,12 @@ abstract class AbstractChangesListSpecialPageTestCase extends MediaWikiTestCase
        /**
         * @dataProvider validateOptionsProvider
         */
-       public function testValidateOptions( $optionsToSet, $expectedRedirect, $expectedRedirectOptions ) {
+       public function testValidateOptions(
+               $optionsToSet,
+               $expectedRedirect,
+               $expectedRedirectOptions,
+               $rcfilters
+       ) {
                $redirectQuery = [];
                $redirected = false;
                $output = $this->getMockBuilder( OutputPage::class )
@@ -110,6 +115,7 @@ abstract class AbstractChangesListSpecialPageTestCase extends MediaWikiTestCase
 
                // 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
index 19a1875..b874215 100644 (file)
@@ -15,13 +15,6 @@ use Wikimedia\TestingAccessWrapper;
  * @covers ChangesListSpecialPage
  */
 class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase {
-       public function setUp() {
-               parent::setUp();
-               $this->setMwGlobals( [
-                       'wgStructuredChangeFiltersShowPreference' => true,
-               ] );
-       }
-
        protected function getPage() {
                $mock = $this->getMockBuilder( ChangesListSpecialPage::class )
                        ->setConstructorArgs(
@@ -1004,57 +997,68 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
                                [ '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,
                        ],
                ];
        }
index 0b6962d..11988da 100644 (file)
@@ -46,6 +46,7 @@ class SpecialRecentchangesTest extends AbstractChangesListSpecialPageTestCase {
                                [ 'hideanons' => 1, 'hideliu' => 1 ],
                                true,
                                [ 'hideliu' => 1 ],
+                               false,
                        ],
                ];
        }