Merge "RCFilters: better vertical alignment of checkbox and text in menus"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 3 Oct 2018 00:43:17 +0000 (00:43 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 3 Oct 2018 00:43:17 +0000 (00:43 +0000)
16 files changed:
includes/cache/MessageCache.php
includes/deferred/LinksUpdate.php
includes/export/WikiExporter.php
includes/libs/rdbms/database/Database.php
includes/libs/rdbms/database/IDatabase.php
includes/page/Article.php
includes/poolcounter/PoolWorkArticleView.php
includes/specials/SpecialExport.php
includes/specials/pagers/ContribsPager.php
languages/Language.php
maintenance/includes/BackupDumper.php
resources/src/mediawiki.special.changeslist.css
tests/phpunit/includes/cache/MessageCacheTest.php
tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php
tests/phpunit/includes/page/ArticleViewTest.php
tests/phpunit/includes/poolcounter/PoolWorkArticleViewTest.php

index aa929bc..bf2ed2e 100644 (file)
@@ -776,13 +776,12 @@ class MessageCache {
         *   - If boolean and false, create object from the current users language
         *   - If boolean and true, create object from the wikis content language
         *   - If language object, use it as given
-        * @param bool $isFullKey Specifies whether $key is a two part key "msg/lang".
         *
         * @throws MWException When given an invalid key
         * @return string|bool False if the message doesn't exist, otherwise the
         *   message (which can be empty)
         */
-       function get( $key, $useDB = true, $langcode = true, $isFullKey = false ) {
+       function get( $key, $useDB = true, $langcode = true ) {
                if ( is_int( $key ) ) {
                        // Fix numerical strings that somehow become ints
                        // on their way here
@@ -794,13 +793,6 @@ class MessageCache {
                        return false;
                }
 
-               // For full keys, get the language code from the key
-               $pos = strrpos( $key, '/' );
-               if ( $isFullKey && $pos !== false ) {
-                       $langcode = substr( $key, $pos + 1 );
-                       $key = substr( $key, 0, $pos );
-               }
-
                // Normalise title-case input (with some inlining)
                $lckey = self::normalizeKey( $key );
 
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 f37364f..5c0a8c7 100644 (file)
@@ -1742,7 +1742,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                        $conds = '';
                }
 
-               if ( $conds === '' ) {
+               if ( $conds === '' || $conds === '*' ) {
                        $sql = "SELECT $startOpts $fields $from $useIndex $ignoreIndex $preLimitTail";
                } elseif ( is_string( $conds ) ) {
                        $sql = "SELECT $startOpts $fields $from $useIndex $ignoreIndex " .
index f97db3a..b1582a1 100644 (file)
@@ -670,6 +670,8 @@ interface IDatabase {
         * Escaping of untrusted input used in values of numeric keys should be done via
         * IDatabase::addQuotes()
         *
+        * Use an empty array, string, or '*' to update all rows.
+        *
         * @param string|array $options
         *
         * Optional: Array of query options. Boolean options are specified by
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 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 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 5897241..8104b2c 100644 (file)
@@ -703,6 +703,14 @@ class Language {
                        }
 
                        $this->namespaceAliases = $aliases + $convertedNames;
+
+                       # Filter out aliases to namespaces that don't exist, e.g. from extensions
+                       # that aren't loaded here but are included in the l10n cache.
+                       # (array_intersect preserves keys from its first argument)
+                       $this->namespaceAliases = array_intersect(
+                               $this->namespaceAliases,
+                               array_keys( $this->getNamespaces() )
+                       );
                }
 
                return $this->namespaceAliases;
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 1edb935..f6ea680 100644 (file)
@@ -129,25 +129,6 @@ class MessageCacheTest extends MediaWikiLangTestCase {
                $this->assertEquals( $oldText, $messageCache->get( $message ), 'Content restored' );
        }
 
-       /**
-        * There's a fallback case where the message key is given as fully qualified -- this
-        * should ignore the passed $lang and use the language from the key
-        *
-        * @dataProvider provideMessagesForFullKeys
-        */
-       public function testFullKeyBehaviour( $message, $lang, $expectedContent ) {
-               $result = MessageCache::singleton()->get( $message, true, $lang, true );
-               $this->assertEquals( $expectedContent, $result, "Full key message fallback failed." );
-       }
-
-       function provideMessagesForFullKeys() {
-               return [
-                       [ 'MessageCacheTest-FullKeyTest/ru', 'ru', 'ru' ],
-                       [ 'MessageCacheTest-FullKeyTest/ru', 'ab', 'ru' ],
-                       [ 'MessageCacheTest-FullKeyTest/ru/foo', 'ru', false ],
-               ];
-       }
-
        /**
         * @dataProvider provideNormalizeKey
         */
index 0cb35b4..600e0d3 100644 (file)
@@ -674,9 +674,9 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                                "INSERT INTO insert_table " .
                                        "(field_insert,field) " .
                                        "SELECT field_select,field2 " .
-                                       "FROM select_table WHERE *",
+                                       "FROM select_table",
                                "SELECT field_select AS field_insert,field2 AS field " .
-                               "FROM select_table WHERE *   FOR UPDATE",
+                               "FROM select_table      FOR UPDATE",
                                "INSERT INTO insert_table (field_insert,field) VALUES ('0','1')"
                        ],
                        [
@@ -755,7 +755,7 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                        __METHOD__
                );
                $this->assertLastSqlDb( implode( '; ', [
-                       'SELECT field2 AS field FROM select_table WHERE *   FOR UPDATE',
+                       'SELECT field2 AS field FROM select_table      FOR UPDATE',
                        'BEGIN',
                        "INSERT INTO insert_table (field) VALUES ('" . implode( "'),('", range( 0, 9999 ) ) . "')",
                        "INSERT INTO insert_table (field) VALUES ('" . implode( "'),('", range( 10000, 19999 ) ) . "')",
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 );