'ExternalStoreHttp' => __DIR__ . '/includes/externalstore/ExternalStoreHttp.php',
'ExternalStoreMedium' => __DIR__ . '/includes/externalstore/ExternalStoreMedium.php',
'ExternalStoreMwstore' => __DIR__ . '/includes/externalstore/ExternalStoreMwstore.php',
- 'FSFile' => __DIR__ . '/includes/libs/filebackend/FSFile.php',
+ 'FSFile' => __DIR__ . '/includes/libs/filebackend/fsfile/FSFile.php',
'FSFileBackend' => __DIR__ . '/includes/libs/filebackend/FSFileBackend.php',
'FSFileBackendDirList' => __DIR__ . '/includes/libs/filebackend/FSFileBackend.php',
'FSFileBackendFileList' => __DIR__ . '/includes/libs/filebackend/FSFileBackend.php',
'TableDiffFormatter' => __DIR__ . '/includes/diff/TableDiffFormatter.php',
'TablePager' => __DIR__ . '/includes/pager/TablePager.php',
'TagLogFormatter' => __DIR__ . '/includes/logging/TagLogFormatter.php',
- 'TempFSFile' => __DIR__ . '/includes/libs/filebackend/TempFSFile.php',
+ 'TempFSFile' => __DIR__ . '/includes/libs/filebackend/fsfile/TempFSFile.php',
'TempFileRepo' => __DIR__ . '/includes/filerepo/FileRepo.php',
'TemplateParser' => __DIR__ . '/includes/TemplateParser.php',
'TemplatesOnThisPageFormatter' => __DIR__ . '/includes/TemplatesOnThisPageFormatter.php',
* or false if populating empty cache fails. Also returns true if MessageCache
* is disabled.
*
- * @param bool|string $code Language to which load messages
- * @param integer $mode Use MessageCache::FOR_UPDATE to skip process cache
+ * @param string $code Language to which load messages
+ * @param integer $mode Use MessageCache::FOR_UPDATE to skip process cache [optional]
* @throws MWException
* @return bool
*/
- function load( $code = false, $mode = null ) {
+ protected function load( $code, $mode = null ) {
if ( !is_string( $code ) ) {
- # This isn't really nice, so at least make a note about it and try to
- # fall back
- wfDebug( __METHOD__ . " called without providing a language code\n" );
- $code = 'en';
+ throw new InvalidArgumentException( "Missing language code" );
}
# Don't do double loading...
}
$alreadyTried[ $langcode ] = true;
}
+ } else {
+ $uckey = null;
}
// Check the CDB cache
continue;
}
- $message = $this->getMsgFromNamespace( $this->getMessagePageName( $code, $uckey ), $code );
+ $message = $this->getMsgFromNamespace(
+ $this->getMessagePageName( $code, $uckey ), $code );
if ( $message !== false ) {
return $message;
* @param Exception|Throwable $e
*/
public static function rollbackMasterChangesAndLog( $e ) {
- $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+ $services = MediaWikiServices::getInstance();
+ if ( $services->isServiceDisabled( 'DBLoadBalancerFactory' ) ) {
+ return; // T147599
+ }
+
+ $lbFactory = $services->getDBLoadBalancerFactory();
if ( $lbFactory->hasMasterChanges() ) {
$logger = LoggerFactory::getInstance( 'Bug56269' );
$logger->warning(
return $status;
}
- /**
- * @var $conn Database
- */
+ /** @var $conn DatabasePostgres */
$conn = $status->value;
if ( $conn->tableExists( 'archive' ) ) {
return $status;
}
- $error = $conn->sourceFile( $conn->getSchemaPath() );
+ $error = $conn->sourceFile( $this->getSchemaPath( $conn ) );
if ( $error !== true ) {
$conn->reportQueryError( $error, 0, '', __METHOD__ );
$conn->rollback( __METHOD__ );
+++ /dev/null
-<?php
-/**
- * Non-directory file on the file system.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup FileBackend
- */
-
-/**
- * Class representing a non-directory file on the file system
- *
- * @ingroup FileBackend
- */
-class FSFile {
- /** @var string Path to file */
- protected $path;
-
- /** @var string File SHA-1 in base 36 */
- protected $sha1Base36;
-
- /**
- * Sets up the file object
- *
- * @param string $path Path to temporary file on local disk
- */
- public function __construct( $path ) {
- $this->path = $path;
- }
-
- /**
- * Returns the file system path
- *
- * @return string
- */
- public function getPath() {
- return $this->path;
- }
-
- /**
- * Checks if the file exists
- *
- * @return bool
- */
- public function exists() {
- return is_file( $this->path );
- }
-
- /**
- * Get the file size in bytes
- *
- * @return int|bool
- */
- public function getSize() {
- return filesize( $this->path );
- }
-
- /**
- * Get the file's last-modified timestamp
- *
- * @return string|bool TS_MW timestamp or false on failure
- */
- public function getTimestamp() {
- MediaWiki\suppressWarnings();
- $timestamp = filemtime( $this->path );
- MediaWiki\restoreWarnings();
- if ( $timestamp !== false ) {
- $timestamp = wfTimestamp( TS_MW, $timestamp );
- }
-
- return $timestamp;
- }
-
- /**
- * Get an associative array containing information about
- * a file with the given storage path.
- *
- * Resulting array fields include:
- * - fileExists
- * - size (filesize in bytes)
- * - mime (as major/minor)
- * - file-mime (as major/minor)
- * - sha1 (in base 36)
- * - major_mime
- * - minor_mime
- *
- * @param string|bool $ext The file extension, or true to extract it from the filename.
- * Set it to false to ignore the extension. Currently unused.
- * @return array
- */
- public function getProps( $ext = true ) {
- $info = self::placeholderProps();
- $info['fileExists'] = $this->exists();
-
- if ( $info['fileExists'] ) {
- $info['size'] = $this->getSize(); // bytes
- $info['sha1'] = $this->getSha1Base36();
-
- $mime = mime_content_type( $this->path );
- # MIME type according to file contents
- $info['file-mime'] = ( $mime === false ) ? 'unknown/unknown' : $mime;
- # logical MIME type
- $info['mime'] = $mime;
-
- if ( strpos( $mime, '/' ) !== false ) {
- list( $info['major_mime'], $info['minor_mime'] ) = explode( '/', $mime, 2 );
- } else {
- list( $info['major_mime'], $info['minor_mime'] ) = [ $mime, 'unknown' ];
- }
- }
-
- return $info;
- }
-
- /**
- * Placeholder file properties to use for files that don't exist
- *
- * Resulting array fields include:
- * - fileExists
- * - size (filesize in bytes)
- * - mime (as major/minor)
- * - file-mime (as major/minor)
- * - sha1 (in base 36)
- * - major_mime
- * - minor_mime
- *
- * @return array
- */
- public static function placeholderProps() {
- $info = [];
- $info['fileExists'] = false;
- $info['size'] = 0;
- $info['file-mime'] = null;
- $info['major_mime'] = null;
- $info['minor_mime'] = null;
- $info['mime'] = null;
- $info['sha1'] = '';
-
- return $info;
- }
-
- /**
- * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
- * encoding, zero padded to 31 digits.
- *
- * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
- * fairly neatly.
- *
- * @param bool $recache
- * @return bool|string False on failure
- */
- public function getSha1Base36( $recache = false ) {
- if ( $this->sha1Base36 !== null && !$recache ) {
- return $this->sha1Base36;
- }
-
- MediaWiki\suppressWarnings();
- $this->sha1Base36 = sha1_file( $this->path );
- MediaWiki\restoreWarnings();
-
- if ( $this->sha1Base36 !== false ) {
- $this->sha1Base36 = Wikimedia\base_convert( $this->sha1Base36, 16, 36, 31 );
- }
-
- return $this->sha1Base36;
- }
-
- /**
- * Get the final file extension from a file system path
- *
- * @param string $path
- * @return string
- */
- public static function extensionFromPath( $path ) {
- $i = strrpos( $path, '.' );
-
- return strtolower( $i ? substr( $path, $i + 1 ) : '' );
- }
-
- /**
- * Get an associative array containing information about a file in the local filesystem.
- *
- * @param string $path Absolute local filesystem path
- * @param string|bool $ext The file extension, or true to extract it from the filename.
- * Set it to false to ignore the extension.
- * @return array
- */
- public static function getPropsFromPath( $path, $ext = true ) {
- $fsFile = new self( $path );
-
- return $fsFile->getProps( $ext );
- }
-
- /**
- * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
- * encoding, zero padded to 31 digits.
- *
- * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
- * fairly neatly.
- *
- * @param string $path
- * @return bool|string False on failure
- */
- public static function getSha1Base36FromPath( $path ) {
- $fsFile = new self( $path );
-
- return $fsFile->getSha1Base36();
- }
-}
+++ /dev/null
-<?php
-/**
- * Location holder of files stored temporarily
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup FileBackend
- */
-
-/**
- * This class is used to hold the location and do limited manipulation
- * of files stored temporarily (this will be whatever wfTempDir() returns)
- *
- * @ingroup FileBackend
- */
-class TempFSFile extends FSFile {
- /** @var bool Garbage collect the temp file */
- protected $canDelete = false;
-
- /** @var array Map of (path => 1) for paths to delete on shutdown */
- protected static $pathsCollect = null;
-
- public function __construct( $path ) {
- parent::__construct( $path );
-
- if ( self::$pathsCollect === null ) {
- self::$pathsCollect = [];
- register_shutdown_function( [ __CLASS__, 'purgeAllOnShutdown' ] );
- }
- }
-
- /**
- * Make a new temporary file on the file system.
- * Temporary files may be purged when the file object falls out of scope.
- *
- * @param string $prefix
- * @param string $extension Optional file extension
- * @param string|null $tmpDirectory Optional parent directory
- * @return TempFSFile|null
- */
- public static function factory( $prefix, $extension = '', $tmpDirectory = null ) {
- $ext = ( $extension != '' ) ? ".{$extension}" : '';
-
- $attempts = 5;
- while ( $attempts-- ) {
- $hex = sprintf( '%06x%06x', mt_rand( 0, 0xffffff ), mt_rand( 0, 0xffffff ) );
- if ( !is_string( $tmpDirectory ) ) {
- $tmpDirectory = self::getUsableTempDirectory();
- }
- $path = wfTempDir() . '/' . $prefix . $hex . $ext;
- MediaWiki\suppressWarnings();
- $newFileHandle = fopen( $path, 'x' );
- MediaWiki\restoreWarnings();
- if ( $newFileHandle ) {
- fclose( $newFileHandle );
- $tmpFile = new self( $path );
- $tmpFile->autocollect();
- // Safely instantiated, end loop.
- return $tmpFile;
- }
- }
-
- // Give up
- return null;
- }
-
- /**
- * @return string Filesystem path to a temporary directory
- * @throws RuntimeException
- */
- public static function getUsableTempDirectory() {
- $tmpDir = array_map( 'getenv', [ 'TMPDIR', 'TMP', 'TEMP' ] );
- $tmpDir[] = sys_get_temp_dir();
- $tmpDir[] = ini_get( 'upload_tmp_dir' );
- foreach ( $tmpDir as $tmp ) {
- if ( $tmp != '' && is_dir( $tmp ) && is_writable( $tmp ) ) {
- return $tmp;
- }
- }
-
- // PHP on Windows will detect C:\Windows\Temp as not writable even though PHP can write to
- // it so create a directory within that called 'mwtmp' with a suffix of the user running
- // the current process.
- // The user is included as if various scripts are run by different users they will likely
- // not be able to access each others temporary files.
- if ( strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN' ) {
- $tmp = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'mwtmp-' . get_current_user();
- if ( !file_exists( $tmp ) ) {
- mkdir( $tmp );
- }
- if ( is_dir( $tmp ) && is_writable( $tmp ) ) {
- return $tmp;
- }
- }
-
- throw new RuntimeException(
- 'No writable temporary directory could be found. ' .
- 'Please explicitly specify a writable directory in configuration.' );
- }
-
- /**
- * Purge this file off the file system
- *
- * @return bool Success
- */
- public function purge() {
- $this->canDelete = false; // done
- MediaWiki\suppressWarnings();
- $ok = unlink( $this->path );
- MediaWiki\restoreWarnings();
-
- unset( self::$pathsCollect[$this->path] );
-
- return $ok;
- }
-
- /**
- * Clean up the temporary file only after an object goes out of scope
- *
- * @param object $object
- * @return TempFSFile This object
- */
- public function bind( $object ) {
- if ( is_object( $object ) ) {
- if ( !isset( $object->tempFSFileReferences ) ) {
- // Init first since $object might use __get() and return only a copy variable
- $object->tempFSFileReferences = [];
- }
- $object->tempFSFileReferences[] = $this;
- }
-
- return $this;
- }
-
- /**
- * Set flag to not clean up after the temporary file
- *
- * @return TempFSFile This object
- */
- public function preserve() {
- $this->canDelete = false;
-
- unset( self::$pathsCollect[$this->path] );
-
- return $this;
- }
-
- /**
- * Set flag clean up after the temporary file
- *
- * @return TempFSFile This object
- */
- public function autocollect() {
- $this->canDelete = true;
-
- self::$pathsCollect[$this->path] = 1;
-
- return $this;
- }
-
- /**
- * Try to make sure that all files are purged on error
- *
- * This method should only be called internally
- */
- public static function purgeAllOnShutdown() {
- foreach ( self::$pathsCollect as $path ) {
- MediaWiki\suppressWarnings();
- unlink( $path );
- MediaWiki\restoreWarnings();
- }
- }
-
- /**
- * Cleans up after the temporary file by deleting it
- */
- function __destruct() {
- if ( $this->canDelete ) {
- $this->purge();
- }
- }
-}
--- /dev/null
+<?php
+/**
+ * Non-directory file on the file system.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup FileBackend
+ */
+
+/**
+ * Class representing a non-directory file on the file system
+ *
+ * @ingroup FileBackend
+ */
+class FSFile {
+ /** @var string Path to file */
+ protected $path;
+
+ /** @var string File SHA-1 in base 36 */
+ protected $sha1Base36;
+
+ /**
+ * Sets up the file object
+ *
+ * @param string $path Path to temporary file on local disk
+ */
+ public function __construct( $path ) {
+ $this->path = $path;
+ }
+
+ /**
+ * Returns the file system path
+ *
+ * @return string
+ */
+ public function getPath() {
+ return $this->path;
+ }
+
+ /**
+ * Checks if the file exists
+ *
+ * @return bool
+ */
+ public function exists() {
+ return is_file( $this->path );
+ }
+
+ /**
+ * Get the file size in bytes
+ *
+ * @return int|bool
+ */
+ public function getSize() {
+ return filesize( $this->path );
+ }
+
+ /**
+ * Get the file's last-modified timestamp
+ *
+ * @return string|bool TS_MW timestamp or false on failure
+ */
+ public function getTimestamp() {
+ MediaWiki\suppressWarnings();
+ $timestamp = filemtime( $this->path );
+ MediaWiki\restoreWarnings();
+ if ( $timestamp !== false ) {
+ $timestamp = wfTimestamp( TS_MW, $timestamp );
+ }
+
+ return $timestamp;
+ }
+
+ /**
+ * Get an associative array containing information about
+ * a file with the given storage path.
+ *
+ * Resulting array fields include:
+ * - fileExists
+ * - size (filesize in bytes)
+ * - mime (as major/minor)
+ * - file-mime (as major/minor)
+ * - sha1 (in base 36)
+ * - major_mime
+ * - minor_mime
+ *
+ * @param string|bool $ext The file extension, or true to extract it from the filename.
+ * Set it to false to ignore the extension. Currently unused.
+ * @return array
+ */
+ public function getProps( $ext = true ) {
+ $info = self::placeholderProps();
+ $info['fileExists'] = $this->exists();
+
+ if ( $info['fileExists'] ) {
+ $info['size'] = $this->getSize(); // bytes
+ $info['sha1'] = $this->getSha1Base36();
+
+ $mime = mime_content_type( $this->path );
+ # MIME type according to file contents
+ $info['file-mime'] = ( $mime === false ) ? 'unknown/unknown' : $mime;
+ # logical MIME type
+ $info['mime'] = $mime;
+
+ if ( strpos( $mime, '/' ) !== false ) {
+ list( $info['major_mime'], $info['minor_mime'] ) = explode( '/', $mime, 2 );
+ } else {
+ list( $info['major_mime'], $info['minor_mime'] ) = [ $mime, 'unknown' ];
+ }
+ }
+
+ return $info;
+ }
+
+ /**
+ * Placeholder file properties to use for files that don't exist
+ *
+ * Resulting array fields include:
+ * - fileExists
+ * - size (filesize in bytes)
+ * - mime (as major/minor)
+ * - file-mime (as major/minor)
+ * - sha1 (in base 36)
+ * - major_mime
+ * - minor_mime
+ *
+ * @return array
+ */
+ public static function placeholderProps() {
+ $info = [];
+ $info['fileExists'] = false;
+ $info['size'] = 0;
+ $info['file-mime'] = null;
+ $info['major_mime'] = null;
+ $info['minor_mime'] = null;
+ $info['mime'] = null;
+ $info['sha1'] = '';
+
+ return $info;
+ }
+
+ /**
+ * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
+ * encoding, zero padded to 31 digits.
+ *
+ * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
+ * fairly neatly.
+ *
+ * @param bool $recache
+ * @return bool|string False on failure
+ */
+ public function getSha1Base36( $recache = false ) {
+ if ( $this->sha1Base36 !== null && !$recache ) {
+ return $this->sha1Base36;
+ }
+
+ MediaWiki\suppressWarnings();
+ $this->sha1Base36 = sha1_file( $this->path );
+ MediaWiki\restoreWarnings();
+
+ if ( $this->sha1Base36 !== false ) {
+ $this->sha1Base36 = Wikimedia\base_convert( $this->sha1Base36, 16, 36, 31 );
+ }
+
+ return $this->sha1Base36;
+ }
+
+ /**
+ * Get the final file extension from a file system path
+ *
+ * @param string $path
+ * @return string
+ */
+ public static function extensionFromPath( $path ) {
+ $i = strrpos( $path, '.' );
+
+ return strtolower( $i ? substr( $path, $i + 1 ) : '' );
+ }
+
+ /**
+ * Get an associative array containing information about a file in the local filesystem.
+ *
+ * @param string $path Absolute local filesystem path
+ * @param string|bool $ext The file extension, or true to extract it from the filename.
+ * Set it to false to ignore the extension.
+ * @return array
+ */
+ public static function getPropsFromPath( $path, $ext = true ) {
+ $fsFile = new self( $path );
+
+ return $fsFile->getProps( $ext );
+ }
+
+ /**
+ * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
+ * encoding, zero padded to 31 digits.
+ *
+ * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
+ * fairly neatly.
+ *
+ * @param string $path
+ * @return bool|string False on failure
+ */
+ public static function getSha1Base36FromPath( $path ) {
+ $fsFile = new self( $path );
+
+ return $fsFile->getSha1Base36();
+ }
+}
--- /dev/null
+<?php
+/**
+ * Location holder of files stored temporarily
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup FileBackend
+ */
+
+/**
+ * This class is used to hold the location and do limited manipulation
+ * of files stored temporarily (this will be whatever wfTempDir() returns)
+ *
+ * @ingroup FileBackend
+ */
+class TempFSFile extends FSFile {
+ /** @var bool Garbage collect the temp file */
+ protected $canDelete = false;
+
+ /** @var array Map of (path => 1) for paths to delete on shutdown */
+ protected static $pathsCollect = null;
+
+ public function __construct( $path ) {
+ parent::__construct( $path );
+
+ if ( self::$pathsCollect === null ) {
+ self::$pathsCollect = [];
+ register_shutdown_function( [ __CLASS__, 'purgeAllOnShutdown' ] );
+ }
+ }
+
+ /**
+ * Make a new temporary file on the file system.
+ * Temporary files may be purged when the file object falls out of scope.
+ *
+ * @param string $prefix
+ * @param string $extension Optional file extension
+ * @param string|null $tmpDirectory Optional parent directory
+ * @return TempFSFile|null
+ */
+ public static function factory( $prefix, $extension = '', $tmpDirectory = null ) {
+ $ext = ( $extension != '' ) ? ".{$extension}" : '';
+
+ $attempts = 5;
+ while ( $attempts-- ) {
+ $hex = sprintf( '%06x%06x', mt_rand( 0, 0xffffff ), mt_rand( 0, 0xffffff ) );
+ if ( !is_string( $tmpDirectory ) ) {
+ $tmpDirectory = self::getUsableTempDirectory();
+ }
+ $path = wfTempDir() . '/' . $prefix . $hex . $ext;
+ MediaWiki\suppressWarnings();
+ $newFileHandle = fopen( $path, 'x' );
+ MediaWiki\restoreWarnings();
+ if ( $newFileHandle ) {
+ fclose( $newFileHandle );
+ $tmpFile = new self( $path );
+ $tmpFile->autocollect();
+ // Safely instantiated, end loop.
+ return $tmpFile;
+ }
+ }
+
+ // Give up
+ return null;
+ }
+
+ /**
+ * @return string Filesystem path to a temporary directory
+ * @throws RuntimeException
+ */
+ public static function getUsableTempDirectory() {
+ $tmpDir = array_map( 'getenv', [ 'TMPDIR', 'TMP', 'TEMP' ] );
+ $tmpDir[] = sys_get_temp_dir();
+ $tmpDir[] = ini_get( 'upload_tmp_dir' );
+ foreach ( $tmpDir as $tmp ) {
+ if ( $tmp != '' && is_dir( $tmp ) && is_writable( $tmp ) ) {
+ return $tmp;
+ }
+ }
+
+ // PHP on Windows will detect C:\Windows\Temp as not writable even though PHP can write to
+ // it so create a directory within that called 'mwtmp' with a suffix of the user running
+ // the current process.
+ // The user is included as if various scripts are run by different users they will likely
+ // not be able to access each others temporary files.
+ if ( strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN' ) {
+ $tmp = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'mwtmp-' . get_current_user();
+ if ( !file_exists( $tmp ) ) {
+ mkdir( $tmp );
+ }
+ if ( is_dir( $tmp ) && is_writable( $tmp ) ) {
+ return $tmp;
+ }
+ }
+
+ throw new RuntimeException(
+ 'No writable temporary directory could be found. ' .
+ 'Please explicitly specify a writable directory in configuration.' );
+ }
+
+ /**
+ * Purge this file off the file system
+ *
+ * @return bool Success
+ */
+ public function purge() {
+ $this->canDelete = false; // done
+ MediaWiki\suppressWarnings();
+ $ok = unlink( $this->path );
+ MediaWiki\restoreWarnings();
+
+ unset( self::$pathsCollect[$this->path] );
+
+ return $ok;
+ }
+
+ /**
+ * Clean up the temporary file only after an object goes out of scope
+ *
+ * @param object $object
+ * @return TempFSFile This object
+ */
+ public function bind( $object ) {
+ if ( is_object( $object ) ) {
+ if ( !isset( $object->tempFSFileReferences ) ) {
+ // Init first since $object might use __get() and return only a copy variable
+ $object->tempFSFileReferences = [];
+ }
+ $object->tempFSFileReferences[] = $this;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set flag to not clean up after the temporary file
+ *
+ * @return TempFSFile This object
+ */
+ public function preserve() {
+ $this->canDelete = false;
+
+ unset( self::$pathsCollect[$this->path] );
+
+ return $this;
+ }
+
+ /**
+ * Set flag clean up after the temporary file
+ *
+ * @return TempFSFile This object
+ */
+ public function autocollect() {
+ $this->canDelete = true;
+
+ self::$pathsCollect[$this->path] = 1;
+
+ return $this;
+ }
+
+ /**
+ * Try to make sure that all files are purged on error
+ *
+ * This method should only be called internally
+ */
+ public static function purgeAllOnShutdown() {
+ foreach ( self::$pathsCollect as $path ) {
+ MediaWiki\suppressWarnings();
+ unlink( $path );
+ MediaWiki\restoreWarnings();
+ }
+ }
+
+ /**
+ * Cleans up after the temporary file by deleting it
+ */
+ function __destruct() {
+ if ( $this->canDelete ) {
+ $this->purge();
+ }
+ }
+}
/** @var callback Error logging callback */
protected $errorLogger;
- /** @var resource Database connection */
+ /** @var resource|null Database connection */
protected $mConn = null;
/** @var bool */
protected $mOpened = false;
}
if ( !isset( $p['errorLogger'] ) ) {
$p['errorLogger'] = function ( Exception $e ) {
- trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_WARNING );
+ trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_USER_WARNING );
};
}
return !!( $this->mFlags & $flag );
}
+ /**
+ * @param string $name Class field name
+ * @return mixed
+ * @deprecated Since 1.28
+ */
public function getProperty( $name ) {
return $this->$name;
}
if ( $this->htmlErrors !== false ) {
ini_set( 'html_errors', $this->htmlErrors );
}
+
+ return $this->getLastPHPError();
+ }
+
+ /**
+ * @return string|bool Last PHP error for this DB (typically connection errors)
+ */
+ protected function getLastPHPError() {
if ( $this->mPHPError ) {
$error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError );
$error = preg_replace( '!^.*?:\s?(.*)$!', '$1', $error );
return $error;
- } else {
- return false;
}
+
+ return false;
}
/**
* @return bool
*/
protected function isTransactableQuery( $sql ) {
- $verb = $this->getQueryVerb( $sql );
- return !in_array( $verb, [ 'BEGIN', 'COMMIT', 'ROLLBACK', 'SHOW', 'SET' ], true );
+ return !in_array(
+ $this->getQueryVerb( $sql ),
+ [ 'BEGIN', 'COMMIT', 'ROLLBACK', 'SHOW', 'SET', 'CREATE', 'ALTER' ],
+ true
+ );
}
/**
$fnames = implode( ', ', $this->pendingWriteAndCallbackCallers() );
throw new DBUnexpectedError(
$this,
- "$fname: Cannot COMMIT to clear snapshot because writes are pending ($fnames)."
+ "$fname: Cannot flush snapshot because writes are pending ($fnames)."
);
}
$fnames = implode( ', ', $this->pendingWriteAndCallbackCallers() );
throw new DBUnexpectedError(
$this,
- "$fname: Cannot COMMIT to clear snapshot because writes are pending ($fnames)."
+ "$fname: Cannot flush pre-lock snapshot because writes are pending ($fnames)."
);
}
return true;
}
+ /**
+ * Get the underlying binding handle, mConn
+ *
+ * Makes sure that mConn is set (disconnects and ping() failure can unset it).
+ * This catches broken callers than catch and ignore disconnection exceptions.
+ * Unlike checking isOpen(), this is safe to call inside of open().
+ *
+ * @return resource|object
+ * @throws DBUnexpectedError
+ * @since 1.26
+ */
+ protected function getBindingHandle() {
+ if ( !$this->mConn ) {
+ throw new DBUnexpectedError(
+ $this,
+ 'DB connection was already closed or the connection dropped.'
+ );
+ }
+
+ return $this->mConn;
+ }
+
/**
* @since 1.19
* @return string
}
if ( $this->mConn ) {
- // Avoid connection leaks for sanity
+ // Avoid connection leaks for sanity. Normally, resources close at script completion.
+ // The connection might already be closed in zend/hhvm by now, so suppress warnings.
+ \MediaWiki\suppressWarnings();
$this->closeConnection();
+ \MediaWiki\restoreWarnings();
$this->mConn = false;
$this->mOpened = false;
}
return $errno == 2013 || $errno == 2006;
}
- /**
- * Get the underlying binding handle, mConn
- *
- * Makes sure that mConn is set (disconnects and ping() failure can unset it).
- * This catches broken callers than catch and ignore disconnection exceptions.
- * Unlike checking isOpen(), this is safe to call inside of open().
- *
- * @return resource|object
- * @throws DBUnexpectedError
- * @since 1.26
- */
- protected function getBindingHandle() {
- if ( !$this->mConn ) {
- throw new DBUnexpectedError(
- $this,
- 'DB connection was already closed or the connection dropped.'
- );
- }
-
- return $this->mConn;
- }
-
/**
* @param string $oldName
* @param string $newName
* @see Database
*/
class DatabaseMysqli extends DatabaseMysqlBase {
- /** @var mysqli */
- protected $mConn;
+ /** @var $mConn mysqli */
/**
* @param string $sql
}
function hasConstraint( $name ) {
+ $conn = $this->getBindingHandle();
+
$sql = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n " .
"WHERE c.connamespace = n.oid AND conname = '" .
- pg_escape_string( $this->mConn, $name ) . "' AND n.nspname = '" .
- pg_escape_string( $this->mConn, $this->getCoreSchema() ) . "'";
+ pg_escape_string( $conn, $name ) . "' AND n.nspname = '" .
+ pg_escape_string( $conn, $this->getCoreSchema() ) . "'";
$res = $this->doQuery( $sql );
return $this->numRows( $res );
);
}
- if ( !strlen( $user ) ) { # e.g. the class is being loaded
- return null;
- }
-
$this->mServer = $server;
$this->mUser = $user;
$this->mPassword = $password;
$this->installErrorHandler();
try {
- $this->mConn = pg_connect( $this->connectString );
+ // Use new connections to let LoadBalancer/LBFactory handle reuse
+ $this->mConn = pg_connect( $this->connectString, PGSQL_CONNECT_FORCE_NEW );
} catch ( Exception $ex ) {
$this->restoreErrorHandler();
throw $ex;
$phpError = $this->restoreErrorHandler();
if ( !$this->mConn ) {
- $this->queryLogger->debug( "DB connection error\n" );
$this->queryLogger->debug(
+ "DB connection error\n" .
"Server: $server, Database: $dbName, User: $user, Password: " .
- substr( $password, 0, 3 ) . "...\n" );
+ substr( $password, 0, 3 ) . "...\n"
+ );
$this->queryLogger->debug( $this->lastError() . "\n" );
throw new DBConnectionError( $this, str_replace( "\n", ' ', $phpError ) );
}
* @return bool
*/
protected function closeConnection() {
- return pg_close( $this->mConn );
+ return $this->mConn ? pg_close( $this->mConn ) : true;
}
public function doQuery( $sql ) {
+ $conn = $this->getBindingHandle();
+
$sql = mb_convert_encoding( $sql, 'UTF-8' );
// Clear previously left over PQresult
- while ( $res = pg_get_result( $this->mConn ) ) {
+ while ( $res = pg_get_result( $conn ) ) {
pg_free_result( $res );
}
- if ( pg_send_query( $this->mConn, $sql ) === false ) {
+ if ( pg_send_query( $conn, $sql ) === false ) {
throw new DBUnexpectedError( $this, "Unable to post new query to PostgreSQL\n" );
}
- $this->mLastResult = pg_get_result( $this->mConn );
+ $this->mLastResult = pg_get_result( $conn );
$this->mAffectedRows = null;
if ( pg_result_error( $this->mLastResult ) ) {
return false;
# @todo hashar: not sure if the following test really trigger if the object
# fetching failed.
- if ( pg_last_error( $this->mConn ) ) {
+ $conn = $this->getBindingHandle();
+ if ( pg_last_error( $conn ) ) {
throw new DBUnexpectedError(
$this,
- 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) )
+ 'SQL error: ' . htmlspecialchars( pg_last_error( $conn ) )
);
}
MediaWiki\suppressWarnings();
$row = pg_fetch_array( $res );
MediaWiki\restoreWarnings();
- if ( pg_last_error( $this->mConn ) ) {
+
+ $conn = $this->getBindingHandle();
+ if ( pg_last_error( $conn ) ) {
throw new DBUnexpectedError(
$this,
- 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) )
+ 'SQL error: ' . htmlspecialchars( pg_last_error( $conn ) )
);
}
MediaWiki\suppressWarnings();
$n = pg_num_rows( $res );
MediaWiki\restoreWarnings();
- if ( pg_last_error( $this->mConn ) ) {
+
+ $conn = $this->getBindingHandle();
+ if ( pg_last_error( $conn ) ) {
throw new DBUnexpectedError(
$this,
- 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) )
+ 'SQL error: ' . htmlspecialchars( pg_last_error( $conn ) )
);
}
} else {
return pg_last_error();
}
- } else {
- return 'No database connection';
}
+
+ return $this->getLastPHPError() ?: 'No database connection';
}
function lastErrno() {
$this->mCoreSchema . "\"\n" );
}
/* Commit SET otherwise it will be rollbacked on error or IGNORE SELECT */
- $this->commit( __METHOD__ );
+ $this->commit( __METHOD__, self::FLUSHING_INTERNAL );
}
/**
*/
function getServerVersion() {
if ( !isset( $this->numericVersion ) ) {
- $versionInfo = pg_version( $this->mConn );
+ $conn = $this->getBindingHandle();
+ $versionInfo = pg_version( $conn );
if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) {
// Old client, abort install
$this->numericVersion = '7.3 or earlier';
$this->numericVersion = $versionInfo['server'];
} else {
// Bug 16937: broken pgsql extension from PHP<5.3
- $this->numericVersion = pg_parameter_status( $this->mConn, 'server_version' );
+ $this->numericVersion = pg_parameter_status( $conn, 'server_version' );
}
}
function strencode( $s ) {
// Should not be called by us
- return pg_escape_string( $this->mConn, $s );
+ return pg_escape_string( $this->getBindingHandle(), $s );
}
/**
* @return string|int
*/
function addQuotes( $s ) {
+ $conn = $this->getBindingHandle();
+
if ( is_null( $s ) ) {
return 'NULL';
} elseif ( is_bool( $s ) ) {
if ( $s instanceof PostgresBlob ) {
$s = $s->fetch();
} else {
- $s = pg_escape_bytea( $this->mConn, $s->fetch() );
+ $s = pg_escape_bytea( $conn, $s->fetch() );
}
return "'$s'";
}
- return "'" . pg_escape_string( $this->mConn, $s ) . "'";
+ return "'" . pg_escape_string( $conn, $s ) . "'";
}
/**
/** @var resource */
protected $mLastResult;
- /** @var PDO */
- protected $mConn;
+ /** @var $mConn PDO */
/** @var FSLockManager (hopefully on the same server as the DB) */
protected $lockMgr;
*/
public function getFlag( $flag );
- /**
- * General read-only accessor
- *
- * @param string $name
- * @return string
- */
- public function getProperty( $name );
-
/**
* @return string
*/
$this->errorLogger = isset( $conf['errorLogger'] )
? $conf['errorLogger']
: function ( Exception $e ) {
- trigger_error( E_WARNING, get_class( $e ) . ': ' . $e->getMessage() );
+ trigger_error( E_USER_WARNING, get_class( $e ) . ': ' . $e->getMessage() );
};
$this->profiler = isset( $params['profiler'] ) ? $params['profiler'] : null;
$this->errorLogger = isset( $params['errorLogger'] )
? $params['errorLogger']
: function ( Exception $e ) {
- trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_WARNING );
+ trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_USER_WARNING );
};
foreach ( [ 'replLogger', 'connLogger', 'queryLogger', 'perfLogger' ] as $key ) {
if ( $i == self::DB_REPLICA ) {
$this->mLastError = 'Unknown error'; // reset error string
# Try the general server pool if $groups are unavailable.
- $i = in_array( false, $groups, true )
+ $i = ( $groups === [ false ] )
? false // don't bother with this if that is what was tried above
: $this->getReaderIndex( false, $domain );
# Couldn't find a working server in getReaderIndex()?
// If all servers were busy, mLastError will contain something sensible
throw new DBConnectionError( null, $this->mLastError );
} else {
- $context['db_server'] = $conn->getProperty( 'mServer' );
+ $context['db_server'] = $conn->getServer();
$this->connLogger->warning(
"Connection error: {last_error} ({db_server})",
$context
* @return string
*/
public function __toString() {
- return $this->getName();
+ return (string)$this->getName();
}
/**
];
public $mMainLanguageCode;
- public $mVariants, $mVariantFallbacks, $mVariantNames;
+ public $mVariants;
+ public $mVariantFallbacks;
+ public $mVariantNames;
public $mTablesLoaded = false;
public $mTables;
// 'bidirectional' 'unidirectional' 'disable' for each variant
* @file
* @ingroup Maintenance
*/
+use MediaWiki\MediaWikiServices;
if ( !defined( 'RUN_MAINTENANCE_IF_MAIN' ) ) {
echo "This file must be included after Maintenance.php\n";
$maintenance->globals();
// Perform deferred updates.
+$lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+$lbFactory->commitMasterChanges( $maintClass );
DeferredUpdates::doUpdates();
// log profiling info
wfLogProfilingData();
// Commit and close up!
-$factory = wfGetLBFactory();
-$factory->commitMasterChanges( 'doMaintenance' );
-$factory->shutdown( $factory::SHUTDOWN_NO_CHRONPROT );
+$lbFactory->commitMasterChanges( 'doMaintenance' );
+$lbFactory->shutdown( $lbFactory::SHUTDOWN_NO_CHRONPROT );