Merge "filebackend: use self:: instead of FileBackend:: for some constant uses"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Sun, 8 Sep 2019 14:38:17 +0000 (14:38 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Sun, 8 Sep 2019 14:38:17 +0000 (14:38 +0000)
15 files changed:
docs/hooks.txt
includes/EditPage.php
includes/FileDeleteForm.php
includes/api/ApiExpandTemplates.php
includes/libs/filebackend/FSFileBackend.php
includes/page/ImagePage.php
includes/page/WikiPage.php
includes/parser/PPFrame_DOM.php
includes/resourceloader/ResourceLoaderStartUpModule.php
includes/watcheditem/NoWriteWatchedItemStore.php
languages/i18n/en.json
languages/i18n/qqq.json
maintenance/preprocessDump.php
maintenance/storage/recompressTracked.php
tests/phpunit/includes/filebackend/FileBackendTest.php

index b7ea02c..a248c29 100644 (file)
@@ -2827,6 +2827,7 @@ or request state must be added through MakeGlobalVariablesScript instead.
 Skin is made available for skin specific config.
 &$vars: [ variable name => value ]
 $skin: Skin
+$config: Config object (since 1.34)
 
 'ResourceLoaderJqueryMsgModuleMagicWords': Called in
 ResourceLoaderJqueryMsgModule to allow adding magic words for jQueryMsg.
index 6ae4371..c346b75 100644 (file)
@@ -1193,6 +1193,8 @@ class EditPage {
         * @since 1.21
         */
        protected function getContentObject( $def_content = null ) {
+               global $wgDisableAnonTalk;
+
                $content = false;
 
                $user = $this->context->getUser();
@@ -1292,8 +1294,11 @@ class EditPage {
                                                                                $undo
                                                                        )->inContentLanguage()->text();
                                                                } else {
+                                                                       $undoMessage = ( $undorev->getUser() === 0 && $wgDisableAnonTalk ) ?
+                                                                               'undo-summary-anon' :
+                                                                               'undo-summary';
                                                                        $undoSummary = $this->context->msg(
-                                                                               'undo-summary',
+                                                                               $undoMessage,
                                                                                $undo,
                                                                                $userText
                                                                        )->inContentLanguage()->text();
index 1241e1c..e31f9d2 100644 (file)
@@ -36,18 +36,18 @@ class FileDeleteForm {
        private $title = null;
 
        /**
-        * @var File
+        * @var LocalFile
         */
        private $file = null;
 
        /**
-        * @var File
+        * @var LocalFile
         */
        private $oldfile = null;
        private $oldimage = '';
 
        /**
-        * @param File $file File object we're deleting
+        * @param LocalFile $file File object we're deleting
         */
        public function __construct( $file ) {
                $this->title = $file->getTitle();
@@ -451,9 +451,9 @@ class FileDeleteForm {
         * value was provided, does it correspond to an
         * existing, local, old version of this file?
         *
-        * @param File &$file
-        * @param File &$oldfile
-        * @param File $oldimage
+        * @param LocalFile &$file
+        * @param LocalFile &$oldfile
+        * @param LocalFile $oldimage
         * @return bool
         */
        public static function haveDeletableFile( &$file, &$oldfile, $oldimage ) {
index a5e7437..4b74a3d 100644 (file)
@@ -76,10 +76,6 @@ class ApiExpandTemplates extends ApiBase {
                                        $this->addWarning( [ 'apierror-revwrongpage', $rev->getId(),
                                                wfEscapeWikiText( $pTitleObj->getPrefixedText() ) ] );
                                }
-                       } else {
-                               // Consider the title derived from the revid as having
-                               // been provided.
-                               $titleProvided = true;
                        }
                }
 
index f23e5cc..5534cbd 100644 (file)
@@ -40,6 +40,7 @@
  * @file
  * @ingroup FileBackend
  */
+use Wikimedia\AtEase\AtEase;
 use Wikimedia\Timestamp\ConvertibleTimestamp;
 
 /**
@@ -63,23 +64,22 @@ class FSFileBackend extends FileBackendStore {
        protected $basePath;
 
        /** @var array Map of container names to root paths for custom container paths */
-       protected $containerPaths = [];
+       protected $containerPaths;
 
+       /** @var int Directory permission mode */
+       protected $dirMode;
        /** @var int File permission mode */
        protected $fileMode;
-       /** @var int File permission mode */
-       protected $dirMode;
-
        /** @var string Required OS username to own files */
        protected $fileOwner;
 
-       /** @var bool */
+       /** @var bool Whether the OS is Windows (otherwise assumed Unix-like)*/
        protected $isWindows;
        /** @var string OS username running this script */
        protected $currentUser;
 
-       /** @var array */
-       protected $hadWarningErrors = [];
+       /** @var bool[] Map of (stack index => whether a warning happened) */
+       private $warningTrapStack = [];
 
        /**
         * @see FileBackendStore::__construct()
@@ -102,11 +102,9 @@ class FSFileBackend extends FileBackendStore {
                        $this->basePath = null; // none; containers must have explicit paths
                }
 
-               if ( isset( $config['containerPaths'] ) ) {
-                       $this->containerPaths = (array)$config['containerPaths'];
-                       foreach ( $this->containerPaths as &$path ) {
-                               $path = rtrim( $path, '/' ); // remove trailing slash
-                       }
+               $this->containerPaths = [];
+               foreach ( ( $config['containerPaths'] ?? [] ) as $container => $path ) {
+                       $this->containerPaths[$container] = rtrim( $path, '/' ); // remove trailing slash
                }
 
                $this->fileMode = $config['fileMode'] ?? 0644;
@@ -228,20 +226,12 @@ class FSFileBackend extends FileBackendStore {
                }
 
                if ( !empty( $params['async'] ) ) { // deferred
-                       $tempFile = $this->tmpFileFactory->newTempFSFile( 'create_', 'tmp' );
+                       $tempFile = $this->stageContentAsTempFile( $params );
                        if ( !$tempFile ) {
                                $status->fatal( 'backend-fail-create', $params['dst'] );
 
                                return $status;
                        }
-                       $this->trapWarnings();
-                       $bytes = file_put_contents( $tempFile->getPath(), $params['content'] );
-                       $this->untrapWarnings();
-                       if ( $bytes === false ) {
-                               $status->fatal( 'backend-fail-create', $params['dst'] );
-
-                               return $status;
-                       }
                        $cmd = implode( ' ', [
                                $this->isWindows ? 'COPY /B /Y' : 'cp', // (binary, overwrite)
                                escapeshellarg( $this->cleanPathSlashes( $tempFile->getPath() ) ),
@@ -376,34 +366,38 @@ class FSFileBackend extends FileBackendStore {
        protected function doMoveInternal( array $params ) {
                $status = $this->newStatus();
 
-               $source = $this->resolveToFSPath( $params['src'] );
-               if ( $source === null ) {
+               $fsSrcPath = $this->resolveToFSPath( $params['src'] );
+               if ( $fsSrcPath === null ) {
                        $status->fatal( 'backend-fail-invalidpath', $params['src'] );
 
                        return $status;
                }
 
-               $dest = $this->resolveToFSPath( $params['dst'] );
-               if ( $dest === null ) {
+               $fsDstPath = $this->resolveToFSPath( $params['dst'] );
+               if ( $fsDstPath === null ) {
                        $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
 
                        return $status;
                }
 
-               if ( !is_file( $source ) ) {
-                       if ( empty( $params['ignoreMissingSource'] ) ) {
-                               $status->fatal( 'backend-fail-move', $params['src'] );
-                       }
-
-                       return $status; // do nothing; either OK or bad status
+               if ( $fsSrcPath === $fsDstPath ) {
+                       return $status; // no-op
                }
 
+               $ignoreMissing = !empty( $params['ignoreMissingSource'] );
+
                if ( !empty( $params['async'] ) ) { // deferred
-                       $cmd = implode( ' ', [
-                               $this->isWindows ? 'MOVE /Y' : 'mv', // (overwrite)
-                               escapeshellarg( $this->cleanPathSlashes( $source ) ),
-                               escapeshellarg( $this->cleanPathSlashes( $dest ) )
-                       ] );
+                       // https://manpages.debian.org/buster/coreutils/mv.1.en.html
+                       // https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/move
+                       $encSrc = escapeshellarg( $this->cleanPathSlashes( $fsSrcPath ) );
+                       $encDst = escapeshellarg( $this->cleanPathSlashes( $fsDstPath ) );
+                       if ( $this->isWindows ) {
+                               $writeCmd = "MOVE /Y $encSrc $encDst";
+                               $cmd = $ignoreMissing ? "IF EXIST $encSrc $writeCmd" : $writeCmd;
+                       } else {
+                               $writeCmd = "mv -f $encSrc $encDst";
+                               $cmd = $ignoreMissing ? "test -f $encSrc && $writeCmd" : $writeCmd;
+                       }
                        $handler = function ( $errors, StatusValue $status, array $params, $cmd ) {
                                if ( $errors !== '' && !( $this->isWindows && $errors[0] === " " ) ) {
                                        $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
@@ -412,11 +406,13 @@ class FSFileBackend extends FileBackendStore {
                        };
                        $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd );
                } else { // immediate write
-                       $this->trapWarnings();
-                       $ok = ( $source === $dest ) ? true : rename( $source, $dest );
-                       $this->untrapWarnings();
-                       clearstatcache(); // file no longer at source
-                       if ( !$ok ) {
+                       // Use rename() here since (a) this clears xattrs, (b) any threads still reading the
+                       // old inode are unaffected since it writes to a new inode, and (c) this is fast and
+                       // atomic within a file system volume (as is normally the case)
+                       $this->trapWarnings( '/: No such file or directory$/' );
+                       $moved = rename( $fsSrcPath, $fsDstPath );
+                       $hadError = $this->untrapWarnings();
+                       if ( $hadError || ( !$moved && !$ignoreMissing ) ) {
                                $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
 
                                return $status;
@@ -429,26 +425,25 @@ class FSFileBackend extends FileBackendStore {
        protected function doDeleteInternal( array $params ) {
                $status = $this->newStatus();
 
-               $source = $this->resolveToFSPath( $params['src'] );
-               if ( $source === null ) {
+               $fsSrcPath = $this->resolveToFSPath( $params['src'] );
+               if ( $fsSrcPath === null ) {
                        $status->fatal( 'backend-fail-invalidpath', $params['src'] );
 
                        return $status;
                }
 
-               if ( !is_file( $source ) ) {
-                       if ( empty( $params['ignoreMissingSource'] ) ) {
-                               $status->fatal( 'backend-fail-delete', $params['src'] );
-                       }
-
-                       return $status; // do nothing; either OK or bad status
-               }
+               $ignoreMissing = !empty( $params['ignoreMissingSource'] );
 
                if ( !empty( $params['async'] ) ) { // deferred
-                       $cmd = implode( ' ', [
-                               $this->isWindows ? 'DEL' : 'unlink',
-                               escapeshellarg( $this->cleanPathSlashes( $source ) )
-                       ] );
+                       // https://manpages.debian.org/buster/coreutils/rm.1.en.html
+                       // https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/del
+                       $encSrc = escapeshellarg( $this->cleanPathSlashes( $fsSrcPath ) );
+                       if ( $this->isWindows ) {
+                               $writeCmd = "DEL /Q $encSrc";
+                               $cmd = $ignoreMissing ? "IF EXIST $encSrc $writeCmd" : $writeCmd;
+                       } else {
+                               $cmd = $ignoreMissing ? "rm -f $encSrc" : "rm $encSrc";
+                       }
                        $handler = function ( $errors, StatusValue $status, array $params, $cmd ) {
                                if ( $errors !== '' && !( $this->isWindows && $errors[0] === " " ) ) {
                                        $status->fatal( 'backend-fail-delete', $params['src'] );
@@ -457,10 +452,10 @@ class FSFileBackend extends FileBackendStore {
                        };
                        $status->value = new FSFileOpHandle( $this, $params, $handler, $cmd );
                } else { // immediate write
-                       $this->trapWarnings();
-                       $ok = unlink( $source );
-                       $this->untrapWarnings();
-                       if ( !$ok ) {
+                       $this->trapWarnings( '/: No such file or directory$/' );
+                       $deleted = unlink( $fsSrcPath );
+                       $hadError = $this->untrapWarnings();
+                       if ( $hadError || ( !$deleted && !$ignoreMissing ) ) {
                                $status->fatal( 'backend-fail-delete', $params['src'] );
 
                                return $status;
@@ -483,7 +478,7 @@ class FSFileBackend extends FileBackendStore {
                $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
                $existed = is_dir( $dir ); // already there?
                // Create the directory and its parents as needed...
-               $this->trapWarnings();
+               AtEase::suppressWarnings();
                if ( !$existed && !mkdir( $dir, $this->dirMode, true ) && !is_dir( $dir ) ) {
                        $this->logger->error( __METHOD__ . ": cannot create directory $dir" );
                        $status->fatal( 'directorycreateerror', $params['dir'] ); // fails on races
@@ -494,7 +489,7 @@ class FSFileBackend extends FileBackendStore {
                        $this->logger->error( __METHOD__ . ": directory $dir is not readable" );
                        $status->fatal( 'directorynotreadableerror', $params['dir'] );
                }
-               $this->untrapWarnings();
+               AtEase::restoreWarnings();
                // Respect any 'noAccess' or 'noListing' flags...
                if ( is_dir( $dir ) && !$existed ) {
                        $status->merge( $this->doSecureInternal( $fullCont, $dirRel, $params ) );
@@ -519,9 +514,9 @@ class FSFileBackend extends FileBackendStore {
                }
                // Add a .htaccess file to the root of the container...
                if ( !empty( $params['noAccess'] ) && !file_exists( "{$contRoot}/.htaccess" ) ) {
-                       $this->trapWarnings();
+                       AtEase::suppressWarnings();
                        $bytes = file_put_contents( "{$contRoot}/.htaccess", $this->htaccessPrivate() );
-                       $this->untrapWarnings();
+                       AtEase::restoreWarnings();
                        if ( $bytes === false ) {
                                $storeDir = "mwstore://{$this->name}/{$shortCont}";
                                $status->fatal( 'backend-fail-create', "{$storeDir}/.htaccess" );
@@ -539,21 +534,17 @@ class FSFileBackend extends FileBackendStore {
                // Unseed new directories with a blank index.html, to allow crawling...
                if ( !empty( $params['listing'] ) && is_file( "{$dir}/index.html" ) ) {
                        $exists = ( file_get_contents( "{$dir}/index.html" ) === $this->indexHtmlPrivate() );
-                       $this->trapWarnings();
-                       if ( $exists && !unlink( "{$dir}/index.html" ) ) { // reverse secure()
+                       if ( $exists && !$this->unlink( "{$dir}/index.html" ) ) { // reverse secure()
                                $status->fatal( 'backend-fail-delete', $params['dir'] . '/index.html' );
                        }
-                       $this->untrapWarnings();
                }
                // Remove the .htaccess file from the root of the container...
                if ( !empty( $params['access'] ) && is_file( "{$contRoot}/.htaccess" ) ) {
                        $exists = ( file_get_contents( "{$contRoot}/.htaccess" ) === $this->htaccessPrivate() );
-                       $this->trapWarnings();
-                       if ( $exists && !unlink( "{$contRoot}/.htaccess" ) ) { // reverse secure()
+                       if ( $exists && !$this->unlink( "{$contRoot}/.htaccess" ) ) { // reverse secure()
                                $storeDir = "mwstore://{$this->name}/{$shortCont}";
                                $status->fatal( 'backend-fail-delete', "{$storeDir}/.htaccess" );
                        }
-                       $this->untrapWarnings();
                }
 
                return $status;
@@ -564,11 +555,11 @@ class FSFileBackend extends FileBackendStore {
                list( , $shortCont, ) = FileBackend::splitStoragePath( $params['dir'] );
                $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
                $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
-               $this->trapWarnings();
+               AtEase::suppressWarnings();
                if ( is_dir( $dir ) ) {
                        rmdir( $dir ); // remove directory if empty
                }
-               $this->untrapWarnings();
+               AtEase::restoreWarnings();
 
                return $status;
        }
@@ -724,7 +715,7 @@ class FSFileBackend extends FileBackendStore {
 
                        $tmpPath = $tmpFile->getPath();
                        // Copy the source file over the temp file
-                       $this->trapWarnings();
+                       $this->trapWarnings(); // don't trust 'false' if there were errors
                        $isFile = is_file( $source ); // regular files only
                        $copySuccess = $isFile ? copy( $source, $tmpPath ) : false;
                        $hadError = $this->untrapWarnings();
@@ -755,8 +746,19 @@ class FSFileBackend extends FileBackendStore {
                $statuses = [];
 
                $pipes = [];
+               $octalPermissions = '0' . decoct( $this->fileMode );
                foreach ( $fileOpHandles as $index => $fileOpHandle ) {
-                       $pipes[$index] = popen( "{$fileOpHandle->cmd} 2>&1", 'r' );
+                       $cmd = "{$fileOpHandle->cmd} 2>&1";
+                       // Add a post-operation chmod command for permissions cleanup if applicable
+                       if (
+                               !$this->isWindows &&
+                               $fileOpHandle->chmodPath !== null &&
+                               strlen( $octalPermissions ) == 4
+                       ) {
+                               $encPath = escapeshellarg( $fileOpHandle->chmodPath );
+                               $cmd .= " && chmod $octalPermissions $encPath 2>/dev/null";
+                       }
+                       $pipes[$index] = popen( $cmd, 'r' );
                }
 
                $errs = [];
@@ -772,12 +774,10 @@ class FSFileBackend extends FileBackendStore {
                        $function = $fileOpHandle->call;
                        $function( $errs[$index], $status, $fileOpHandle->params, $fileOpHandle->cmd );
                        $statuses[$index] = $status;
-                       if ( $status->isOK() && $fileOpHandle->chmodPath ) {
-                               $this->chmod( $fileOpHandle->chmodPath );
-                       }
                }
 
                clearstatcache(); // files changed
+
                return $statuses;
        }
 
@@ -788,13 +788,52 @@ class FSFileBackend extends FileBackendStore {
         * @return bool Success
         */
        protected function chmod( $path ) {
-               $this->trapWarnings();
+               if ( $this->isWindows ) {
+                       return true;
+               }
+
+               AtEase::suppressWarnings();
                $ok = chmod( $path, $this->fileMode );
-               $this->untrapWarnings();
+               AtEase::restoreWarnings();
 
                return $ok;
        }
 
+       /**
+        * Unlink a file, suppressing the warnings
+        *
+        * @param string $path Absolute file system path
+        * @return bool Success
+        */
+       protected function unlink( $path ) {
+               AtEase::suppressWarnings();
+               $ok = unlink( $path );
+               AtEase::restoreWarnings();
+
+               return $ok;
+       }
+
+       /**
+        * @param array $params Operation parameters with 'content' and 'headers' fields
+        * @return TempFSFile|null
+        */
+       protected function stageContentAsTempFile( array $params ) {
+               $content = $params['content'];
+               $tempFile = $this->tmpFileFactory->newTempFSFile( 'create_', 'tmp' );
+               if ( !$tempFile ) {
+                       return null;
+               }
+
+               AtEase::suppressWarnings();
+               $tmpPath = $tempFile->getPath();
+               if ( file_put_contents( $tmpPath, $content ) === false ) {
+                       $tempFile = null;
+               }
+               AtEase::restoreWarnings();
+
+               return $tempFile;
+       }
+
        /**
         * Return the text of an index.html file to hide directory listings
         *
@@ -824,30 +863,29 @@ class FSFileBackend extends FileBackendStore {
        }
 
        /**
-        * Listen for E_WARNING errors and track whether any happen
+        * Listen for E_WARNING errors and track whether any that happen
+        *
+        * @param string|null $regexIgnore Optional regex of errors to ignore
         */
-       protected function trapWarnings() {
-               // push to stack
-               $this->hadWarningErrors[] = false;
-               set_error_handler( function ( $errno, $errstr ) {
-                       // more detailed error logging
-                       $this->logger->error( $errstr );
-                       $this->hadWarningErrors[count( $this->hadWarningErrors ) - 1] = true;
-
-                       // suppress from PHP handler
-                       return true;
+       protected function trapWarnings( $regexIgnore = null ) {
+               $this->warningTrapStack[] = false;
+               set_error_handler( function ( $errno, $errstr ) use ( $regexIgnore ) {
+                       if ( $regexIgnore === null || !preg_match( $regexIgnore, $errstr ) ) {
+                               $this->logger->error( $errstr );
+                               $this->warningTrapStack[count( $this->warningTrapStack ) - 1] = true;
+                       }
+                       return true; // suppress from PHP handler
                }, E_WARNING );
        }
 
        /**
-        * Stop listening for E_WARNING errors and return true if any happened
+        * Stop listening for E_WARNING errors and get whether any happened
         *
-        * @return bool
+        * @return bool Whether any warnings happened
         */
        protected function untrapWarnings() {
-               // restore previous handler
                restore_error_handler();
-               // pop from stack
-               return array_pop( $this->hadWarningErrors );
+
+               return array_pop( $this->warningTrapStack );
        }
 }
index 2f6d4da..653e443 100644 (file)
@@ -987,6 +987,7 @@ EOT
                        parent::delete();
                        return;
                }
+               '@phan-var LocalFile $file';
 
                $deleter = new FileDeleteForm( $file );
                $deleter->execute();
index 9c5c4e0..fea1bf6 100644 (file)
@@ -3147,7 +3147,7 @@ class WikiPage implements Page, IDBAccessObject {
        public function commitRollback( $fromP, $summary, $bot,
                &$resultDetails, User $guser, $tags = null
        ) {
-               global $wgUseRCPatrol;
+               global $wgUseRCPatrol, $wgDisableAnonTalk;
 
                $dbw = wfGetDB( DB_MASTER );
 
@@ -3220,6 +3220,8 @@ class WikiPage implements Page, IDBAccessObject {
                if ( empty( $summary ) ) {
                        if ( !$currentEditorForPublic ) { // no public user name
                                $summary = wfMessage( 'revertpage-nouser' );
+                       } elseif ( $wgDisableAnonTalk && $current->getUser() === 0 ) {
+                               $summary = wfMessage( 'revertpage-anon' );
                        } else {
                                $summary = wfMessage( 'revertpage' );
                        }
index ac3a266..bb310f2 100644 (file)
@@ -153,7 +153,7 @@ class PPFrame_DOM implements PPFrame {
 
        /**
         * @throws MWException
-        * @param string|PPNode_DOM|DOMNode $root
+        * @param string|PPNode_DOM|DOMNode|DOMNodeList $root
         * @param int $flags
         * @return string
         */
index d4a34f3..9f583a5 100644 (file)
@@ -121,7 +121,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                        'wgCommentCodePointLimit' => CommentStore::COMMENT_CHARACTER_LIMIT,
                ];
 
-               Hooks::run( 'ResourceLoaderGetConfigVars', [ &$vars, $skin ] );
+               Hooks::run( 'ResourceLoaderGetConfigVars', [ &$vars, $skin, $conf ] );
 
                return $vars;
        }
index 72f6086..69dcec8 100644 (file)
@@ -57,7 +57,10 @@ class NoWriteWatchedItemStore implements WatchedItemStoreInterface {
        }
 
        public function countWatchersMultiple( array $targets, array $options = [] ) {
-               return $this->actualStore->countVisitingWatchersMultiple( $targets, $options );
+               return $this->actualStore->countVisitingWatchersMultiple(
+                       $targets,
+                       $options['minimumWatchers'] ?? null
+               );
        }
 
        public function countVisitingWatchersMultiple(
index 816839c..f6ea46c 100644 (file)
        "undo-norev": "The edit could not be undone because it does not exist or was deleted.",
        "undo-nochange": "The edit appears to have already been undone.",
        "undo-summary": "Undo revision $1 by [[Special:Contributions/$2|$2]] ([[User talk:$2|talk]])",
+       "undo-summary-anon": "Undo revision $1 by [[Special:Contributions/$2|$2]]",
        "undo-summary-username-hidden": "Undo revision $1 by a hidden user",
        "cantcreateaccount-text": "Account creation from this IP address (<strong>$1</strong>) has been blocked by [[User:$3|$3]].\n\nThe reason given by $3 is <em>$2</em>",
        "cantcreateaccount-range-text": "Account creation from IP addresses in the range <strong>$1</strong>, which includes your IP address (<strong>$4</strong>), has been blocked by [[User:$3|$3]].\n\nThe reason given by $3 is <em>$2</em>",
        "alreadyrolled": "Cannot rollback last edit of [[:$1]] by [[User:$2|$2]] ([[User talk:$2|talk]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]);\nsomeone else has edited or rolled back the page already.\n\nThe last edit to the page was by [[User:$3|$3]] ([[User talk:$3|talk]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).",
        "editcomment": "The edit summary was: <em>$1</em>.",
        "revertpage": "Reverted edits by [[Special:Contributions/$2|$2]] ([[User talk:$2|talk]]) to last revision by [[User:$1|$1]]",
+       "revertpage-anon": "Reverted edits by [[Special:Contributions/$2|$2]] to last revision by [[User:$1|$1]]",
        "revertpage-nouser": "Reverted edits by a hidden user to last revision by {{GENDER:$1|[[User:$1|$1]]}}",
        "rollback-success": "Reverted edits by {{GENDER:$3|$1}};\nchanged back to last revision by {{GENDER:$4|$2}}.",
        "sessionfailure-title": "Session failure",
index 5f0b966..420912d 100644 (file)
        "undo-main-slot-only": "Message appears if an attempt to revert an edit by clicking the \"undo\" link on the page history fails because it involves content outside the page's main slot, which is not yet supported.\nThis message is temporary, see https://phabricator.wikimedia.org/T189808\n\nSee also:\n* {{msg-mw|Undo-failure}}\n{{Identical|Undo}}",
        "undo-norev": "Message appears if an attempt to revert an edit by clicking the \"undo\" link on the page history fails.\n\nSee also:\n* {{msg-mw|Undo-failure}}\n* {{msg-mw|Undo-nochange}}\n{{Identical|Undo}}",
        "undo-nochange": "Message appears if an attempt to revert an edit by clicking the \"undo\" link results in an edit making no change to the current version of the page.\n\nSee also:\n* {{msg-mw|Undo-failure}}\n* {{msg-mw|Undo-norev}}",
-       "undo-summary": "Edit summary for an undo action. Parameters:\n* $1 - revision ID\n* $2 - username\n{{Identical|Undo}}",
+       "undo-summary": "Edit summary for an undo action. Parameters:\n* $1 - revision ID\n* $2 - username\n{{Identical|Undo}}\nSee also:\n* {{msg-mw|Undo-summary-anon}}",
+       "undo-summary-anon": "Edit summary for an undo action, when undoing an edit by an anonymous user and $wgDisableAnonTalk is activated. Parameters:\n* $1 - revision ID\n* $2 - username\nSee also:\n* {{msg-mw|Undo-summary}}",
        "undo-summary-username-hidden": "Edit summary for an undo action where the username of the old revision is hidden.\n\nParameters:\n* $1 - the revision ID being undone\nSee also:\n* {{msg-mw|Undo-summary}}",
        "cantcreateaccount-text": "Used as error message when account creation is prevented by an IP block.\n* $1 - target IP address\n* $2 - reason or {{msg-mw|Blockednoreason}}\n* $3 - username\nSee also:\n* {{msg-mw|Cantcreateaccount-range-text}}",
        "cantcreateaccount-range-text": "Used instead of the {{msg-mw|Cantcreateaccount-text}} when the block is a range block.\n* $1 - target IP address range\n* $2 - reason or {{msg-mw|Blockednoreason}}\n* $3 - username\n* $4 - current user's IP address",
        "cantrollback": "Used as error message when rollback fails due to there not being a valid revision to revert back to.\n\nSee also:\n* {{msg-mw|Notvisiblerev}}\n{{Identical|Revert}}\n{{Identical|Rollback}}",
        "alreadyrolled": "Appear when there's rollback and/or edit collision.\n\nRefers to:\n* {{msg-mw|Pipe-separator}}\n* {{msg-mw|Contribslink}}\nParameters:\n* $1 - the page to be rolled back\n* $2 - the editor to be rolled-back of that page\n* $3 - the editor that cause collision\n{{Identical|Rollback}}",
        "editcomment": "Only shown if there is an edit {{msg-mw|Summary}}. Parameters:\n* $1 - the edit summary",
-       "revertpage": "Parameters:\n* $1 - username 1\n* $2 - username 2\n* $3 - (Optional) revision ID of the revision reverted to\n* $4 - (Optional) timestamp of the revision reverted to\n* $5 - (Optional) revision ID of the revision reverted from\n* $6 - (Optional) timestamp of the revision reverted from\nSee also:\n* {{msg-mw|Revertpage-nouser}}\n{{Identical|Revert}}",
-       "revertpage-nouser": "This is a confirmation message a user sees after reverting, when the username of the version is hidden with RevisionDelete.\n\nIn other cases the message {{msg-mw|Revertpage}} is used.\n\nParameters:\n* $1 - username 1, can be used for GENDER\n* $2 - (Optional) username 2\n* $3 - (Optional) revision ID of the revision reverted to\n* $4 - (Optional) timestamp of the revision reverted to\n* $5 - (Optional) revision ID of the revision reverted from\n* $6 - (Optional) timestamp of the revision reverted from",
+       "revertpage": "Parameters:\n* $1 - username 1\n* $2 - username 2\n* $3 - (Optional) revision ID of the revision reverted to\n* $4 - (Optional) timestamp of the revision reverted to\n* $5 - (Optional) revision ID of the revision reverted from\n* $6 - (Optional) timestamp of the revision reverted from\nSee also:\n* {{msg-mw|Revertpage-anon}}\n* {{msg-mw|Revertpage-nouser}}\n{{Identical|Revert}}",
+       "revertpage-anon": "Parameters:\n* $1 - username 1\n* $2 - username 2\n* $3 - (Optional) revision ID of the revision reverted to\n* $4 - (Optional) timestamp of the revision reverted to\n* $5 - (Optional) revision ID of the revision reverted from\n* $6 - (Optional) timestamp of the revision reverted from\nSee also:\n* {{msg-mw|Revertpage}}\n* {{msg-mw|Revertpage-nouser}}\n{{Identical|Revert}}",
+       "revertpage-nouser": "This is a confirmation message a user sees after reverting, when the username of the version is hidden with RevisionDelete.\n\nIn other cases the message {{msg-mw|Revertpage}} or {{msg-mw|Revertpage-anon}} is used.\n\nParameters:\n* $1 - username 1, can be used for GENDER\n* $2 - (Optional) username 2\n* $3 - (Optional) revision ID of the revision reverted to\n* $4 - (Optional) timestamp of the revision reverted to\n* $5 - (Optional) revision ID of the revision reverted from\n* $6 - (Optional) timestamp of the revision reverted from",
        "rollback-success": "This message shows up on screen after successful revert (generally visible only to admins). Parameters:\n* $1 - user whose changes have been reverted\n* $2 - user who produced version, which replaces reverted version\n* $3 - the first user's name, can be used for GENDER\n* $4 - the second user's name, can be used for GENDER\n{{Identical|Revert}}\n{{Identical|Rollback}}",
        "sessionfailure-title": "Used as title of the error message {{msg-mw|Sessionfailure}}.",
        "sessionfailure": "Used as error message.\n\nThe title for this error message is {{msg-mw|Sessionfailure-title}}.",
index 963bfec..05b78d4 100644 (file)
@@ -73,8 +73,9 @@ class PreprocessDump extends DumpIterator {
                        $name = Preprocessor_DOM::class;
                }
 
-               MediaWikiServices::getInstance()->getParser()->firstCallInit();
-               $this->mPreprocessor = new $name( $this );
+               $parser = MediaWikiServices::getInstance()->getParser();
+               $parser->firstCallInit();
+               $this->mPreprocessor = new $name( $parser );
        }
 
        /**
index 316d2d2..0d506ef 100644 (file)
@@ -275,7 +275,7 @@ class RecompressTracked {
        /**
         * Dispatch a command to the next available replica DB.
         * This may block until a replica DB finishes its work and becomes available.
-        * @param array ...$args
+        * @param array|string ...$args
         */
        function dispatch( ...$args ) {
                $pipes = $this->replicaPipes;
index 062087d..3b30d7e 100644 (file)
@@ -942,8 +942,7 @@ class FileBackendTest extends MediaWikiTestCase {
                        "$base/unittest-cont1/e/fileB.a",
                        "$base/unittest-cont1/e/fileC.a"
                ];
-               $createOps = [];
-               $purgeOps = [];
+               $createOps = $copyOps = $moveOps = $deleteOps = [];
                foreach ( $files as $path ) {
                        $status = $this->prepare( [ 'dir' => dirname( $path ) ] );
                        $this->assertGoodStatus( $status,
@@ -951,10 +950,21 @@ class FileBackendTest extends MediaWikiTestCase {
                        $createOps[] = [ 'op' => 'create', 'dst' => $path, 'content' => mt_rand( 0, 50000 ) ];
                        $copyOps[] = [ 'op' => 'copy', 'src' => $path, 'dst' => "$path-2" ];
                        $moveOps[] = [ 'op' => 'move', 'src' => "$path-2", 'dst' => "$path-3" ];
-                       $purgeOps[] = [ 'op' => 'delete', 'src' => $path ];
-                       $purgeOps[] = [ 'op' => 'delete', 'src' => "$path-3" ];
+                       $moveOps[] = [
+                               'op' => 'move',
+                               'src' => "$path-nothing",
+                               'dst' => "$path-nowhere",
+                               'ignoreMissingSource' => true
+                       ];
+                       $deleteOps[] = [ 'op' => 'delete', 'src' => $path ];
+                       $deleteOps[] = [ 'op' => 'delete', 'src' => "$path-3" ];
+                       $deleteOps[] = [
+                               'op' => 'delete',
+                               'src' => "$path-gone",
+                               'ignoreMissingSource' => true
+                       ];
                }
-               $purgeOps[] = [ 'op' => 'null' ];
+               $deleteOps[] = [ 'op' => 'null' ];
 
                $this->assertGoodStatus(
                        $this->backend->doQuickOperations( $createOps ),
@@ -995,7 +1005,7 @@ class FileBackendTest extends MediaWikiTestCase {
                        "File {$files[0]} still exists." );
 
                $this->assertGoodStatus(
-                       $this->backend->doQuickOperations( $purgeOps ),
+                       $this->backend->doQuickOperations( $deleteOps ),
                        "Quick deletion of source files succeeded ($backendName)." );
                foreach ( $files as $file ) {
                        $this->assertFalse( $this->backend->fileExists( [ 'src' => $file ] ),