Localisation updates from https://translatewiki.net.
[mediawiki.git] / includes / installer / MysqlInstaller.php
blobda1ff7bff786b6241f12c6cb1abe0bf99e522677
1 <?php
3 /**
4 * MySQL-specific installer.
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * http://www.gnu.org/copyleft/gpl.html
21 * @file
22 * @ingroup Installer
25 namespace MediaWiki\Installer;
27 use Wikimedia\Rdbms\DatabaseFactory;
28 use Wikimedia\Rdbms\DatabaseMySQL;
29 use Wikimedia\Rdbms\DBConnectionError;
30 use Wikimedia\Rdbms\IDatabase;
32 /**
33 * Class for setting up the MediaWiki database using MySQL.
35 * @ingroup Installer
36 * @since 1.17
38 class MysqlInstaller extends DatabaseInstaller {
40 /** @inheritDoc */
41 protected $globalNames = [
42 'wgDBserver',
43 'wgDBname',
44 'wgDBuser',
45 'wgDBpassword',
46 'wgDBssl',
47 'wgDBprefix',
48 'wgDBTableOptions',
51 /** @inheritDoc */
52 protected $internalDefaults = [
53 '_MysqlEngine' => 'InnoDB',
54 '_MysqlCharset' => 'binary',
55 '_InstallUser' => 'root',
58 /** @var string[] */
59 public $supportedEngines = [ 'InnoDB' ];
61 private const MIN_VERSIONS = [
62 'MySQL' => '5.7.0',
63 'MariaDB' => '10.3',
65 /** @inheritDoc */
66 public static $minimumVersion;
67 /** @inheritDoc */
68 protected static $notMinimumVersionMessage;
70 /** @var string[] */
71 public $webUserPrivs = [
72 'DELETE',
73 'INSERT',
74 'SELECT',
75 'UPDATE',
76 'CREATE TEMPORARY TABLES',
79 /**
80 * @return string
82 public function getName() {
83 return 'mysql';
86 /**
87 * @return bool
89 public function isCompiled() {
90 return self::checkExtension( 'mysqli' );
93 public function getConnectForm( WebInstaller $webInstaller ): DatabaseConnectForm {
94 return new MysqlConnectForm( $webInstaller, $this );
97 public function getSettingsForm( WebInstaller $webInstaller ): DatabaseSettingsForm {
98 return new MysqlSettingsForm( $webInstaller, $this );
101 public static function meetsMinimumRequirement( IDatabase $conn ) {
102 $type = str_contains( $conn->getSoftwareLink(), 'MariaDB' ) ? 'MariaDB' : 'MySQL';
103 self::$minimumVersion = self::MIN_VERSIONS[$type];
104 // Used messages: config-mysql-old, config-mariadb-old
105 self::$notMinimumVersionMessage = 'config-' . strtolower( $type ) . '-old';
106 return parent::meetsMinimumRequirement( $conn );
110 * @param string $type
111 * @return ConnectionStatus
113 protected function openConnection( string $type ) {
114 $status = new ConnectionStatus;
115 $dbName = $type === DatabaseInstaller::CONN_CREATE_DATABASE
116 ? null : $this->getVar( 'wgDBname' );
117 try {
118 /** @var DatabaseMySQL $db */
119 $db = ( new DatabaseFactory() )->create( 'mysql', [
120 'host' => $this->getVar( 'wgDBserver' ),
121 'user' => $this->getVar( '_InstallUser' ),
122 'password' => $this->getVar( '_InstallPassword' ),
123 'ssl' => $this->getVar( 'wgDBssl' ),
124 'dbname' => $dbName,
125 'flags' => 0,
126 'tablePrefix' => $this->getVar( 'wgDBprefix' ) ] );
127 $status->setDB( $db );
128 } catch ( DBConnectionError $e ) {
129 $status->fatal( 'config-connection-error', $e->getMessage() );
132 return $status;
135 public function preUpgrade() {
136 global $wgDBuser, $wgDBpassword;
138 $status = $this->getConnection( self::CONN_CREATE_TABLES );
139 if ( !$status->isOK() ) {
140 $this->parent->showStatusMessage( $status );
142 return;
144 $conn = $status->getDB();
145 # Determine existing default character set
146 if ( $conn->tableExists( "revision", __METHOD__ ) ) {
147 $revision = $this->escapeLikeInternal( $this->getVar( 'wgDBprefix' ) . 'revision', '\\' );
148 $res = $conn->query( "SHOW TABLE STATUS LIKE '$revision'", __METHOD__ );
149 $row = $res->fetchObject();
150 if ( !$row ) {
151 $this->parent->showMessage( 'config-show-table-status' );
152 $existingSchema = false;
153 $existingEngine = false;
154 } else {
155 if ( preg_match( '/^latin1/', $row->Collation ) ) {
156 $existingSchema = 'latin1';
157 } elseif ( preg_match( '/^utf8/', $row->Collation ) ) {
158 $existingSchema = 'utf8';
159 } elseif ( preg_match( '/^binary/', $row->Collation ) ) {
160 $existingSchema = 'binary';
161 } else {
162 $existingSchema = false;
163 $this->parent->showMessage( 'config-unknown-collation' );
165 $existingEngine = $row->Engine ?? $row->Type;
167 } else {
168 $existingSchema = false;
169 $existingEngine = false;
172 if ( $existingSchema && $existingSchema != $this->getVar( '_MysqlCharset' ) ) {
173 $this->setVar( '_MysqlCharset', $existingSchema );
175 if ( $existingEngine && $existingEngine != $this->getVar( '_MysqlEngine' ) ) {
176 $this->setVar( '_MysqlEngine', $existingEngine );
179 # Normal user and password are selected after this step, so for now
180 # just copy these two
181 $wgDBuser = $this->getVar( '_InstallUser' );
182 $wgDBpassword = $this->getVar( '_InstallPassword' );
186 * @param string $s
187 * @param string $escapeChar
188 * @return string
190 protected function escapeLikeInternal( $s, $escapeChar = '`' ) {
191 return str_replace( [ $escapeChar, '%', '_' ],
192 [ "{$escapeChar}{$escapeChar}", "{$escapeChar}%", "{$escapeChar}_" ],
193 $s );
197 * Get a list of storage engines that are available and supported
199 * @return array
201 public function getEngines() {
202 $status = $this->getConnection( self::CONN_CREATE_DATABASE );
203 $conn = $status->getDB();
205 $engines = [];
206 $res = $conn->query( 'SHOW ENGINES', __METHOD__ );
207 foreach ( $res as $row ) {
208 if ( $row->Support == 'YES' || $row->Support == 'DEFAULT' ) {
209 $engines[] = $row->Engine;
212 $engines = array_intersect( $this->supportedEngines, $engines );
214 return $engines;
218 * Get a list of character sets that are available and supported
220 * @return array
222 public function getCharsets() {
223 return [ 'binary', 'utf8' ];
227 * Return true if the install user can create accounts
229 * @return bool
231 public function canCreateAccounts() {
232 $status = $this->getConnection( self::CONN_CREATE_DATABASE );
233 if ( !$status->isOK() ) {
234 return false;
236 $conn = $status->getDB();
238 // Get current account name
239 $currentName = $conn->selectField( '', 'CURRENT_USER()', '', __METHOD__ );
240 $parts = explode( '@', $currentName );
241 if ( count( $parts ) != 2 ) {
242 return false;
244 $quotedUser = $conn->addQuotes( $parts[0] ) .
245 '@' . $conn->addQuotes( $parts[1] );
247 // The user needs to have INSERT on mysql.* to be able to CREATE USER
248 // The grantee will be double-quoted in this query, as required
249 $res = $conn->select( 'INFORMATION_SCHEMA.USER_PRIVILEGES', '*',
250 [ 'GRANTEE' => $quotedUser ], __METHOD__ );
251 $insertMysql = false;
252 $grantOptions = array_fill_keys( $this->webUserPrivs, true );
253 foreach ( $res as $row ) {
254 if ( $row->PRIVILEGE_TYPE == 'INSERT' ) {
255 $insertMysql = true;
257 if ( $row->IS_GRANTABLE ) {
258 unset( $grantOptions[$row->PRIVILEGE_TYPE] );
262 // Check for DB-specific privs for mysql.*
263 if ( !$insertMysql ) {
264 $row = $conn->selectRow( 'INFORMATION_SCHEMA.SCHEMA_PRIVILEGES', '*',
266 'GRANTEE' => $quotedUser,
267 'TABLE_SCHEMA' => 'mysql',
268 'PRIVILEGE_TYPE' => 'INSERT',
269 ], __METHOD__ );
270 if ( $row ) {
271 $insertMysql = true;
275 if ( !$insertMysql ) {
276 return false;
279 // Check for DB-level grant options
280 $res = $conn->select( 'INFORMATION_SCHEMA.SCHEMA_PRIVILEGES', '*',
282 'GRANTEE' => $quotedUser,
283 'IS_GRANTABLE' => 1,
284 ], __METHOD__ );
285 foreach ( $res as $row ) {
286 $regex = $this->likeToRegex( $row->TABLE_SCHEMA );
287 if ( preg_match( $regex, $this->getVar( 'wgDBname' ) ) ) {
288 unset( $grantOptions[$row->PRIVILEGE_TYPE] );
291 if ( count( $grantOptions ) ) {
292 // Can't grant everything
293 return false;
296 return true;
300 * Convert a wildcard (as used in LIKE) to a regex
301 * Slashes are escaped, slash terminators included
302 * @param string $wildcard
303 * @return string
305 protected function likeToRegex( $wildcard ) {
306 $r = preg_quote( $wildcard, '/' );
307 $r = strtr( $r, [
308 '%' => '.*',
309 '_' => '.'
310 ] );
311 return "/$r/s";
315 * Return any table options to be applied to all tables that don't
316 * override them.
318 * @return string
320 protected function getTableOptions() {
321 $options = [];
322 if ( $this->getVar( '_MysqlEngine' ) !== null ) {
323 $options[] = "ENGINE=" . $this->getVar( '_MysqlEngine' );
325 if ( $this->getVar( '_MysqlCharset' ) !== null ) {
326 $options[] = 'DEFAULT CHARSET=' . $this->getVar( '_MysqlCharset' );
329 return implode( ', ', $options );
333 * Get variables to substitute into the SQL schema and patch files.
335 * @return array
337 public function getSchemaVars() {
338 return [
339 'wgDBTableOptions' => $this->getTableOptions(),
343 public function getLocalSettings() {
344 $prefix = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgDBprefix' ) );
345 $useSsl = $this->getVar( 'wgDBssl' ) ? 'true' : 'false';
346 $tblOpts = LocalSettingsGenerator::escapePhpString( $this->getTableOptions() );
348 return "# MySQL specific settings
349 \$wgDBprefix = \"{$prefix}\";
350 \$wgDBssl = {$useSsl};
352 # MySQL table options to use during installation or update
353 \$wgDBTableOptions = \"{$tblOpts}\";";