Merge "Convert article delete to use OOUI"
[lhc/web/wiklou.git] / includes / installer / MssqlInstaller.php
1 <?php
2 /**
3 * Microsoft SQL Server-specific installer.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @ingroup Deployment
22 */
23
24 use Wikimedia\Rdbms\Database;
25 use Wikimedia\Rdbms\DBQueryError;
26 use Wikimedia\Rdbms\DBConnectionError;
27
28 /**
29 * Class for setting up the MediaWiki database using Microsoft SQL Server.
30 *
31 * @ingroup Deployment
32 * @since 1.23
33 */
34 class MssqlInstaller extends DatabaseInstaller {
35
36 protected $globalNames = [
37 'wgDBserver',
38 'wgDBname',
39 'wgDBuser',
40 'wgDBpassword',
41 'wgDBmwschema',
42 'wgDBprefix',
43 'wgDBWindowsAuthentication',
44 ];
45
46 protected $internalDefaults = [
47 '_InstallUser' => 'sa',
48 '_InstallWindowsAuthentication' => 'sqlauth',
49 '_WebWindowsAuthentication' => 'sqlauth',
50 ];
51
52 // SQL Server 2005 RTM
53 // @todo Are SQL Express version numbers different?)
54 public $minimumVersion = '9.00.1399';
55
56 // These are schema-level privs
57 // Note: the web user will be created will full permissions if possible, this permission
58 // list is only used if we are unable to grant full permissions.
59 public $webUserPrivs = [
60 'DELETE',
61 'INSERT',
62 'SELECT',
63 'UPDATE',
64 'EXECUTE',
65 ];
66
67 /**
68 * @return string
69 */
70 public function getName() {
71 return 'mssql';
72 }
73
74 /**
75 * @return bool
76 */
77 public function isCompiled() {
78 return self::checkExtension( 'sqlsrv' );
79 }
80
81 /**
82 * @return string
83 */
84 public function getConnectForm() {
85 if ( $this->getVar( '_InstallWindowsAuthentication' ) == 'windowsauth' ) {
86 $displayStyle = 'display: none;';
87 } else {
88 $displayStyle = 'display: block;';
89 }
90
91 return $this->getTextBox(
92 'wgDBserver',
93 'config-db-host',
94 [],
95 $this->parent->getHelpBox( 'config-db-host-help' )
96 ) .
97 Html::openElement( 'fieldset' ) .
98 Html::element( 'legend', [], wfMessage( 'config-db-wiki-settings' )->text() ) .
99 $this->getTextBox( 'wgDBname', 'config-db-name', [ 'dir' => 'ltr' ],
100 $this->parent->getHelpBox( 'config-db-name-help' ) ) .
101 $this->getTextBox( 'wgDBmwschema', 'config-db-schema', [ 'dir' => 'ltr' ],
102 $this->parent->getHelpBox( 'config-db-schema-help' ) ) .
103 $this->getTextBox( 'wgDBprefix', 'config-db-prefix', [ 'dir' => 'ltr' ],
104 $this->parent->getHelpBox( 'config-db-prefix-help' ) ) .
105 Html::closeElement( 'fieldset' ) .
106 Html::openElement( 'fieldset' ) .
107 Html::element( 'legend', [], wfMessage( 'config-db-install-account' )->text() ) .
108 $this->getRadioSet( [
109 'var' => '_InstallWindowsAuthentication',
110 'label' => 'config-mssql-auth',
111 'itemLabelPrefix' => 'config-mssql-',
112 'values' => [ 'sqlauth', 'windowsauth' ],
113 'itemAttribs' => [
114 'sqlauth' => [
115 'class' => 'showHideRadio',
116 'rel' => 'dbCredentialBox',
117 ],
118 'windowsauth' => [
119 'class' => 'hideShowRadio',
120 'rel' => 'dbCredentialBox',
121 ]
122 ],
123 'help' => $this->parent->getHelpBox( 'config-mssql-install-auth' )
124 ] ) .
125 Html::openElement( 'div', [ 'id' => 'dbCredentialBox', 'style' => $displayStyle ] ) .
126 $this->getTextBox(
127 '_InstallUser',
128 'config-db-username',
129 [ 'dir' => 'ltr' ],
130 $this->parent->getHelpBox( 'config-db-install-username' )
131 ) .
132 $this->getPasswordBox(
133 '_InstallPassword',
134 'config-db-password',
135 [ 'dir' => 'ltr' ],
136 $this->parent->getHelpBox( 'config-db-install-password' )
137 ) .
138 Html::closeElement( 'div' ) .
139 Html::closeElement( 'fieldset' );
140 }
141
142 public function submitConnectForm() {
143 // Get variables from the request.
144 $newValues = $this->setVarsFromRequest( [
145 'wgDBserver',
146 'wgDBname',
147 'wgDBmwschema',
148 'wgDBprefix'
149 ] );
150
151 // Validate them.
152 $status = Status::newGood();
153 if ( !strlen( $newValues['wgDBserver'] ) ) {
154 $status->fatal( 'config-missing-db-host' );
155 }
156 if ( !strlen( $newValues['wgDBname'] ) ) {
157 $status->fatal( 'config-missing-db-name' );
158 } elseif ( !preg_match( '/^[a-z0-9_]+$/i', $newValues['wgDBname'] ) ) {
159 $status->fatal( 'config-invalid-db-name', $newValues['wgDBname'] );
160 }
161 if ( !preg_match( '/^[a-z0-9_]*$/i', $newValues['wgDBmwschema'] ) ) {
162 $status->fatal( 'config-invalid-schema', $newValues['wgDBmwschema'] );
163 }
164 if ( !preg_match( '/^[a-z0-9_]*$/i', $newValues['wgDBprefix'] ) ) {
165 $status->fatal( 'config-invalid-db-prefix', $newValues['wgDBprefix'] );
166 }
167 if ( !$status->isOK() ) {
168 return $status;
169 }
170
171 // Check for blank schema and remap to dbo
172 if ( $newValues['wgDBmwschema'] === '' ) {
173 $this->setVar( 'wgDBmwschema', 'dbo' );
174 }
175
176 // User box
177 $this->setVarsFromRequest( [
178 '_InstallUser',
179 '_InstallPassword',
180 '_InstallWindowsAuthentication'
181 ] );
182
183 // Try to connect
184 $status = $this->getConnection();
185 if ( !$status->isOK() ) {
186 return $status;
187 }
188 /**
189 * @var $conn Database
190 */
191 $conn = $status->value;
192
193 // Check version
194 $version = $conn->getServerVersion();
195 if ( version_compare( $version, $this->minimumVersion ) < 0 ) {
196 return Status::newFatal( 'config-mssql-old', $this->minimumVersion, $version );
197 }
198
199 return $status;
200 }
201
202 /**
203 * @return Status
204 */
205 public function openConnection() {
206 global $wgDBWindowsAuthentication;
207 $status = Status::newGood();
208 $user = $this->getVar( '_InstallUser' );
209 $password = $this->getVar( '_InstallPassword' );
210
211 if ( $this->getVar( '_InstallWindowsAuthentication' ) == 'windowsauth' ) {
212 // Use Windows authentication for this connection
213 $wgDBWindowsAuthentication = true;
214 } else {
215 $wgDBWindowsAuthentication = false;
216 }
217
218 try {
219 $db = Database::factory( 'mssql', [
220 'host' => $this->getVar( 'wgDBserver' ),
221 'port' => $this->getVar( 'wgDBport' ),
222 'user' => $user,
223 'password' => $password,
224 'dbname' => false,
225 'flags' => 0,
226 'schema' => $this->getVar( 'wgDBmwschema' ),
227 'tablePrefix' => $this->getVar( 'wgDBprefix' ) ] );
228 $db->prepareStatements( false );
229 $db->scrollableCursor( false );
230 $status->value = $db;
231 } catch ( DBConnectionError $e ) {
232 $status->fatal( 'config-connection-error', $e->getMessage() );
233 }
234
235 return $status;
236 }
237
238 public function preUpgrade() {
239 global $wgDBuser, $wgDBpassword;
240
241 $status = $this->getConnection();
242 if ( !$status->isOK() ) {
243 $this->parent->showStatusError( $status );
244
245 return;
246 }
247 /**
248 * @var $conn Database
249 */
250 $conn = $status->value;
251 $conn->selectDB( $this->getVar( 'wgDBname' ) );
252
253 # Normal user and password are selected after this step, so for now
254 # just copy these two
255 $wgDBuser = $this->getVar( '_InstallUser' );
256 $wgDBpassword = $this->getVar( '_InstallPassword' );
257 }
258
259 /**
260 * Return true if the install user can create accounts
261 *
262 * @return bool
263 */
264 public function canCreateAccounts() {
265 $status = $this->getConnection();
266 if ( !$status->isOK() ) {
267 return false;
268 }
269 /** @var $conn Database */
270 $conn = $status->value;
271
272 // We need the server-level ALTER ANY LOGIN permission to create new accounts
273 $res = $conn->query( "SELECT permission_name FROM sys.fn_my_permissions( NULL, 'SERVER' )" );
274 $serverPrivs = [
275 'ALTER ANY LOGIN' => false,
276 'CONTROL SERVER' => false,
277 ];
278
279 foreach ( $res as $row ) {
280 $serverPrivs[$row->permission_name] = true;
281 }
282
283 if ( !$serverPrivs['ALTER ANY LOGIN'] ) {
284 return false;
285 }
286
287 // Check to ensure we can grant everything needed as well
288 // We can't actually tell if we have WITH GRANT OPTION for a given permission, so we assume we do
289 // and just check for the permission
290 // https://technet.microsoft.com/en-us/library/ms178569.aspx
291 // The following array sets up which permissions imply whatever permissions we specify
292 $implied = [
293 // schema database server
294 'DELETE' => [ 'DELETE', 'CONTROL SERVER' ],
295 'EXECUTE' => [ 'EXECUTE', 'CONTROL SERVER' ],
296 'INSERT' => [ 'INSERT', 'CONTROL SERVER' ],
297 'SELECT' => [ 'SELECT', 'CONTROL SERVER' ],
298 'UPDATE' => [ 'UPDATE', 'CONTROL SERVER' ],
299 ];
300
301 $grantOptions = array_flip( $this->webUserPrivs );
302
303 // Check for schema and db-level permissions, but only if the schema/db exists
304 $schemaPrivs = $dbPrivs = [
305 'DELETE' => false,
306 'EXECUTE' => false,
307 'INSERT' => false,
308 'SELECT' => false,
309 'UPDATE' => false,
310 ];
311
312 $dbPrivs['ALTER ANY USER'] = false;
313
314 if ( $this->databaseExists( $this->getVar( 'wgDBname' ) ) ) {
315 $conn->selectDB( $this->getVar( 'wgDBname' ) );
316 $res = $conn->query( "SELECT permission_name FROM sys.fn_my_permissions( NULL, 'DATABASE' )" );
317
318 foreach ( $res as $row ) {
319 $dbPrivs[$row->permission_name] = true;
320 }
321
322 // If the db exists, we need ALTER ANY USER privs on it to make a new user
323 if ( !$dbPrivs['ALTER ANY USER'] ) {
324 return false;
325 }
326
327 if ( $this->schemaExists( $this->getVar( 'wgDBmwschema' ) ) ) {
328 // wgDBmwschema is validated to only contain alphanumeric + underscore, so this is safe
329 $res = $conn->query( "SELECT permission_name FROM sys.fn_my_permissions( "
330 . "'{$this->getVar( 'wgDBmwschema' )}', 'SCHEMA' )" );
331
332 foreach ( $res as $row ) {
333 $schemaPrivs[$row->permission_name] = true;
334 }
335 }
336 }
337
338 // Now check all the grants we'll need to be doing to see if we can
339 foreach ( $this->webUserPrivs as $permission ) {
340 if ( ( isset( $schemaPrivs[$permission] ) && $schemaPrivs[$permission] )
341 || ( isset( $dbPrivs[$implied[$permission][0]] )
342 && $dbPrivs[$implied[$permission][0]] )
343 || ( isset( $serverPrivs[$implied[$permission][1]] )
344 && $serverPrivs[$implied[$permission][1]] )
345 ) {
346 unset( $grantOptions[$permission] );
347 }
348 }
349
350 if ( count( $grantOptions ) ) {
351 // Can't grant everything
352 return false;
353 }
354
355 return true;
356 }
357
358 /**
359 * @return string
360 */
361 public function getSettingsForm() {
362 if ( $this->canCreateAccounts() ) {
363 $noCreateMsg = false;
364 } else {
365 $noCreateMsg = 'config-db-web-no-create-privs';
366 }
367
368 $wrapperStyle = $this->getVar( '_SameAccount' ) ? 'display: none' : '';
369 $displayStyle = $this->getVar( '_WebWindowsAuthentication' ) == 'windowsauth'
370 ? 'display: none'
371 : '';
372 $s = Html::openElement( 'fieldset' ) .
373 Html::element( 'legend', [], wfMessage( 'config-db-web-account' )->text() ) .
374 $this->getCheckBox(
375 '_SameAccount', 'config-db-web-account-same',
376 [ 'class' => 'hideShowRadio', 'rel' => 'dbOtherAccount' ]
377 ) .
378 Html::openElement( 'div', [ 'id' => 'dbOtherAccount', 'style' => $wrapperStyle ] ) .
379 $this->getRadioSet( [
380 'var' => '_WebWindowsAuthentication',
381 'label' => 'config-mssql-auth',
382 'itemLabelPrefix' => 'config-mssql-',
383 'values' => [ 'sqlauth', 'windowsauth' ],
384 'itemAttribs' => [
385 'sqlauth' => [
386 'class' => 'showHideRadio',
387 'rel' => 'dbCredentialBox',
388 ],
389 'windowsauth' => [
390 'class' => 'hideShowRadio',
391 'rel' => 'dbCredentialBox',
392 ]
393 ],
394 'help' => $this->parent->getHelpBox( 'config-mssql-web-auth' )
395 ] ) .
396 Html::openElement( 'div', [ 'id' => 'dbCredentialBox', 'style' => $displayStyle ] ) .
397 $this->getTextBox( 'wgDBuser', 'config-db-username' ) .
398 $this->getPasswordBox( 'wgDBpassword', 'config-db-password' ) .
399 Html::closeElement( 'div' );
400
401 if ( $noCreateMsg ) {
402 $s .= $this->parent->getWarningBox( wfMessage( $noCreateMsg )->plain() );
403 } else {
404 $s .= $this->getCheckBox( '_CreateDBAccount', 'config-db-web-create' );
405 }
406
407 $s .= Html::closeElement( 'div' ) . Html::closeElement( 'fieldset' );
408
409 return $s;
410 }
411
412 /**
413 * @return Status
414 */
415 public function submitSettingsForm() {
416 $this->setVarsFromRequest( [
417 'wgDBuser',
418 'wgDBpassword',
419 '_SameAccount',
420 '_CreateDBAccount',
421 '_WebWindowsAuthentication'
422 ] );
423
424 if ( $this->getVar( '_SameAccount' ) ) {
425 $this->setVar( '_WebWindowsAuthentication', $this->getVar( '_InstallWindowsAuthentication' ) );
426 $this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
427 $this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
428 }
429
430 if ( $this->getVar( '_WebWindowsAuthentication' ) == 'windowsauth' ) {
431 $this->setVar( 'wgDBuser', '' );
432 $this->setVar( 'wgDBpassword', '' );
433 $this->setVar( 'wgDBWindowsAuthentication', true );
434 } else {
435 $this->setVar( 'wgDBWindowsAuthentication', false );
436 }
437
438 if ( $this->getVar( '_CreateDBAccount' )
439 && $this->getVar( '_WebWindowsAuthentication' ) == 'sqlauth'
440 && strval( $this->getVar( 'wgDBpassword' ) ) == ''
441 ) {
442 return Status::newFatal( 'config-db-password-empty', $this->getVar( 'wgDBuser' ) );
443 }
444
445 // Validate the create checkbox
446 $canCreate = $this->canCreateAccounts();
447 if ( !$canCreate ) {
448 $this->setVar( '_CreateDBAccount', false );
449 $create = false;
450 } else {
451 $create = $this->getVar( '_CreateDBAccount' );
452 }
453
454 if ( !$create ) {
455 // Test the web account
456 $user = $this->getVar( 'wgDBuser' );
457 $password = $this->getVar( 'wgDBpassword' );
458
459 if ( $this->getVar( '_WebWindowsAuthentication' ) == 'windowsauth' ) {
460 $user = 'windowsauth';
461 $password = 'windowsauth';
462 }
463
464 try {
465 Database::factory( 'mssql', [
466 'host' => $this->getVar( 'wgDBserver' ),
467 'user' => $user,
468 'password' => $password,
469 'dbname' => false,
470 'flags' => 0,
471 'tablePrefix' => $this->getVar( 'wgDBprefix' ),
472 'schema' => $this->getVar( 'wgDBmwschema' ),
473 ] );
474 } catch ( DBConnectionError $e ) {
475 return Status::newFatal( 'config-connection-error', $e->getMessage() );
476 }
477 }
478
479 return Status::newGood();
480 }
481
482 public function preInstall() {
483 # Add our user callback to installSteps, right before the tables are created.
484 $callback = [
485 'name' => 'user',
486 'callback' => [ $this, 'setupUser' ],
487 ];
488 $this->parent->addInstallStep( $callback, 'tables' );
489 }
490
491 /**
492 * @return Status
493 */
494 public function setupDatabase() {
495 $status = $this->getConnection();
496 if ( !$status->isOK() ) {
497 return $status;
498 }
499 /** @var Database $conn */
500 $conn = $status->value;
501 $dbName = $this->getVar( 'wgDBname' );
502 $schemaName = $this->getVar( 'wgDBmwschema' );
503 if ( !$this->databaseExists( $dbName ) ) {
504 $conn->query(
505 "CREATE DATABASE " . $conn->addIdentifierQuotes( $dbName ),
506 __METHOD__
507 );
508 }
509 $conn->selectDB( $dbName );
510 if ( !$this->schemaExists( $schemaName ) ) {
511 $conn->query(
512 "CREATE SCHEMA " . $conn->addIdentifierQuotes( $schemaName ),
513 __METHOD__
514 );
515 }
516 if ( !$this->catalogExists( $schemaName ) ) {
517 $conn->query(
518 "CREATE FULLTEXT CATALOG " . $conn->addIdentifierQuotes( $schemaName ),
519 __METHOD__
520 );
521 }
522 $this->setupSchemaVars();
523
524 return $status;
525 }
526
527 /**
528 * @return Status
529 */
530 public function setupUser() {
531 $dbUser = $this->getVar( 'wgDBuser' );
532 if ( $dbUser == $this->getVar( '_InstallUser' )
533 || ( $this->getVar( '_InstallWindowsAuthentication' ) == 'windowsauth'
534 && $this->getVar( '_WebWindowsAuthentication' ) == 'windowsauth' ) ) {
535 return Status::newGood();
536 }
537 $status = $this->getConnection();
538 if ( !$status->isOK() ) {
539 return $status;
540 }
541
542 $this->setupSchemaVars();
543 $dbName = $this->getVar( 'wgDBname' );
544 $this->db->selectDB( $dbName );
545 $password = $this->getVar( 'wgDBpassword' );
546 $schemaName = $this->getVar( 'wgDBmwschema' );
547
548 if ( $this->getVar( '_WebWindowsAuthentication' ) == 'windowsauth' ) {
549 $dbUser = 'windowsauth';
550 $password = 'windowsauth';
551 }
552
553 if ( $this->getVar( '_CreateDBAccount' ) ) {
554 $tryToCreate = true;
555 } else {
556 $tryToCreate = false;
557 }
558
559 $escUser = $this->db->addIdentifierQuotes( $dbUser );
560 $escDb = $this->db->addIdentifierQuotes( $dbName );
561 $escSchema = $this->db->addIdentifierQuotes( $schemaName );
562 $grantableNames = [];
563 if ( $tryToCreate ) {
564 $escPass = $this->db->addQuotes( $password );
565
566 if ( !$this->loginExists( $dbUser ) ) {
567 try {
568 $this->db->begin();
569 $this->db->selectDB( 'master' );
570 $logintype = $this->getVar( '_WebWindowsAuthentication' ) == 'windowsauth'
571 ? 'FROM WINDOWS'
572 : "WITH PASSWORD = $escPass";
573 $this->db->query( "CREATE LOGIN $escUser $logintype" );
574 $this->db->selectDB( $dbName );
575 $this->db->query( "CREATE USER $escUser FOR LOGIN $escUser WITH DEFAULT_SCHEMA = $escSchema" );
576 $this->db->commit();
577 $grantableNames[] = $dbUser;
578 } catch ( DBQueryError $dqe ) {
579 $this->db->rollback();
580 $status->warning( 'config-install-user-create-failed', $dbUser, $dqe->getMessage() );
581 }
582 } elseif ( !$this->userExists( $dbUser ) ) {
583 try {
584 $this->db->begin();
585 $this->db->selectDB( $dbName );
586 $this->db->query( "CREATE USER $escUser FOR LOGIN $escUser WITH DEFAULT_SCHEMA = $escSchema" );
587 $this->db->commit();
588 $grantableNames[] = $dbUser;
589 } catch ( DBQueryError $dqe ) {
590 $this->db->rollback();
591 $status->warning( 'config-install-user-create-failed', $dbUser, $dqe->getMessage() );
592 }
593 } else {
594 $status->warning( 'config-install-user-alreadyexists', $dbUser );
595 $grantableNames[] = $dbUser;
596 }
597 }
598
599 // Try to grant to all the users we know exist or we were able to create
600 $this->db->selectDB( $dbName );
601 foreach ( $grantableNames as $name ) {
602 try {
603 // First try to grant full permissions
604 $fullPrivArr = [
605 'BACKUP DATABASE', 'BACKUP LOG', 'CREATE FUNCTION', 'CREATE PROCEDURE',
606 'CREATE TABLE', 'CREATE VIEW', 'CREATE FULLTEXT CATALOG', 'SHOWPLAN'
607 ];
608 $fullPrivList = implode( ', ', $fullPrivArr );
609 $this->db->begin();
610 $this->db->query( "GRANT $fullPrivList ON DATABASE :: $escDb TO $escUser", __METHOD__ );
611 $this->db->query( "GRANT CONTROL ON SCHEMA :: $escSchema TO $escUser", __METHOD__ );
612 $this->db->commit();
613 } catch ( DBQueryError $dqe ) {
614 // If that fails, try to grant the limited subset specified in $this->webUserPrivs
615 try {
616 $privList = implode( ', ', $this->webUserPrivs );
617 $this->db->rollback();
618 $this->db->begin();
619 $this->db->query( "GRANT $privList ON SCHEMA :: $escSchema TO $escUser", __METHOD__ );
620 $this->db->commit();
621 } catch ( DBQueryError $dqe ) {
622 $this->db->rollback();
623 $status->fatal( 'config-install-user-grant-failed', $dbUser, $dqe->getMessage() );
624 }
625 // Also try to grant SHOWPLAN on the db, but don't fail if we can't
626 // (just makes a couple things in mediawiki run slower since
627 // we have to run SELECT COUNT(*) instead of getting the query plan)
628 try {
629 $this->db->query( "GRANT SHOWPLAN ON DATABASE :: $escDb TO $escUser", __METHOD__ );
630 } catch ( DBQueryError $dqe ) {
631 }
632 }
633 }
634
635 return $status;
636 }
637
638 public function createTables() {
639 $status = parent::createTables();
640
641 // Do last-minute stuff like fulltext indexes (since they can't be inside a transaction)
642 if ( $status->isOK() ) {
643 $searchindex = $this->db->tableName( 'searchindex' );
644 $schema = $this->db->addIdentifierQuotes( $this->getVar( 'wgDBmwschema' ) );
645 try {
646 $this->db->query( "CREATE FULLTEXT INDEX ON $searchindex (si_title, si_text) "
647 . "KEY INDEX si_page ON $schema" );
648 } catch ( DBQueryError $dqe ) {
649 $status->fatal( 'config-install-tables-failed', $dqe->getMessage() );
650 }
651 }
652
653 return $status;
654 }
655
656 public function getGlobalDefaults() {
657 // The default $wgDBmwschema is null, which breaks Postgres and other DBMSes that require
658 // the use of a schema, so we need to set it here
659 return array_merge( parent::getGlobalDefaults(), [
660 'wgDBmwschema' => 'mediawiki',
661 ] );
662 }
663
664 /**
665 * Try to see if the login exists
666 * @param string $user Username to check
667 * @return bool
668 */
669 private function loginExists( $user ) {
670 $res = $this->db->selectField( 'sys.sql_logins', 1, [ 'name' => $user ] );
671 return (bool)$res;
672 }
673
674 /**
675 * Try to see if the user account exists
676 * We assume we already have the appropriate database selected
677 * @param string $user Username to check
678 * @return bool
679 */
680 private function userExists( $user ) {
681 $res = $this->db->selectField( 'sys.sysusers', 1, [ 'name' => $user ] );
682 return (bool)$res;
683 }
684
685 /**
686 * Try to see if a given database exists
687 * @param string $dbName Database name to check
688 * @return bool
689 */
690 private function databaseExists( $dbName ) {
691 $res = $this->db->selectField( 'sys.databases', 1, [ 'name' => $dbName ] );
692 return (bool)$res;
693 }
694
695 /**
696 * Try to see if a given schema exists
697 * We assume we already have the appropriate database selected
698 * @param string $schemaName Schema name to check
699 * @return bool
700 */
701 private function schemaExists( $schemaName ) {
702 $res = $this->db->selectField( 'sys.schemas', 1, [ 'name' => $schemaName ] );
703 return (bool)$res;
704 }
705
706 /**
707 * Try to see if a given fulltext catalog exists
708 * We assume we already have the appropriate database selected
709 * @param string $catalogName Catalog name to check
710 * @return bool
711 */
712 private function catalogExists( $catalogName ) {
713 $res = $this->db->selectField( 'sys.fulltext_catalogs', 1, [ 'name' => $catalogName ] );
714 return (bool)$res;
715 }
716
717 /**
718 * Get variables to substitute into tables.sql and the SQL patch files.
719 *
720 * @return array
721 */
722 public function getSchemaVars() {
723 return [
724 'wgDBname' => $this->getVar( 'wgDBname' ),
725 'wgDBmwschema' => $this->getVar( 'wgDBmwschema' ),
726 'wgDBuser' => $this->getVar( 'wgDBuser' ),
727 'wgDBpassword' => $this->getVar( 'wgDBpassword' ),
728 ];
729 }
730
731 public function getLocalSettings() {
732 $schema = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgDBmwschema' ) );
733 $prefix = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgDBprefix' ) );
734 $windowsauth = $this->getVar( 'wgDBWindowsAuthentication' ) ? 'true' : 'false';
735
736 return "# MSSQL specific settings
737 \$wgDBWindowsAuthentication = {$windowsauth};
738 \$wgDBmwschema = \"{$schema}\";
739 \$wgDBprefix = \"{$prefix}\";";
740 }
741 }