If MediaWiki has increased the minimum database server version that is required, check it when running update.php to ensure it is still compatible. Previously this was only checked during the installer. Bug: T162044 Change-Id: I47092c9557f4706a4dcb3a23150647e68af4317f
737 lines
22 KiB
PHP
737 lines
22 KiB
PHP
<?php
|
|
/**
|
|
* Microsoft SQL Server-specific installer.
|
|
*
|
|
* 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 Deployment
|
|
*/
|
|
|
|
use Wikimedia\Rdbms\Database;
|
|
use Wikimedia\Rdbms\DBQueryError;
|
|
use Wikimedia\Rdbms\DBConnectionError;
|
|
|
|
/**
|
|
* Class for setting up the MediaWiki database using Microsoft SQL Server.
|
|
*
|
|
* @ingroup Deployment
|
|
* @since 1.23
|
|
*/
|
|
class MssqlInstaller extends DatabaseInstaller {
|
|
|
|
protected $globalNames = [
|
|
'wgDBserver',
|
|
'wgDBname',
|
|
'wgDBuser',
|
|
'wgDBpassword',
|
|
'wgDBmwschema',
|
|
'wgDBprefix',
|
|
'wgDBWindowsAuthentication',
|
|
];
|
|
|
|
protected $internalDefaults = [
|
|
'_InstallUser' => 'sa',
|
|
'_InstallWindowsAuthentication' => 'sqlauth',
|
|
'_WebWindowsAuthentication' => 'sqlauth',
|
|
];
|
|
|
|
// SQL Server 2005 RTM
|
|
// @todo Are SQL Express version numbers different?)
|
|
public static $minimumVersion = '9.00.1399';
|
|
protected static $notMiniumumVerisonMessage = 'config-mssql-old';
|
|
|
|
// These are schema-level privs
|
|
// Note: the web user will be created will full permissions if possible, this permission
|
|
// list is only used if we are unable to grant full permissions.
|
|
public $webUserPrivs = [
|
|
'DELETE',
|
|
'INSERT',
|
|
'SELECT',
|
|
'UPDATE',
|
|
'EXECUTE',
|
|
];
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getName() {
|
|
return 'mssql';
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function isCompiled() {
|
|
return self::checkExtension( 'sqlsrv' );
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getConnectForm() {
|
|
if ( $this->getVar( '_InstallWindowsAuthentication' ) == 'windowsauth' ) {
|
|
$displayStyle = 'display: none;';
|
|
} else {
|
|
$displayStyle = 'display: block;';
|
|
}
|
|
|
|
return $this->getTextBox(
|
|
'wgDBserver',
|
|
'config-db-host',
|
|
[],
|
|
$this->parent->getHelpBox( 'config-db-host-help' )
|
|
) .
|
|
Html::openElement( 'fieldset' ) .
|
|
Html::element( 'legend', [], wfMessage( 'config-db-wiki-settings' )->text() ) .
|
|
$this->getTextBox( 'wgDBname', 'config-db-name', [ 'dir' => 'ltr' ],
|
|
$this->parent->getHelpBox( 'config-db-name-help' ) ) .
|
|
$this->getTextBox( 'wgDBmwschema', 'config-db-schema', [ 'dir' => 'ltr' ],
|
|
$this->parent->getHelpBox( 'config-db-schema-help' ) ) .
|
|
$this->getTextBox( 'wgDBprefix', 'config-db-prefix', [ 'dir' => 'ltr' ],
|
|
$this->parent->getHelpBox( 'config-db-prefix-help' ) ) .
|
|
Html::closeElement( 'fieldset' ) .
|
|
Html::openElement( 'fieldset' ) .
|
|
Html::element( 'legend', [], wfMessage( 'config-db-install-account' )->text() ) .
|
|
$this->getRadioSet( [
|
|
'var' => '_InstallWindowsAuthentication',
|
|
'label' => 'config-mssql-auth',
|
|
'itemLabelPrefix' => 'config-mssql-',
|
|
'values' => [ 'sqlauth', 'windowsauth' ],
|
|
'itemAttribs' => [
|
|
'sqlauth' => [
|
|
'class' => 'showHideRadio',
|
|
'rel' => 'dbCredentialBox',
|
|
],
|
|
'windowsauth' => [
|
|
'class' => 'hideShowRadio',
|
|
'rel' => 'dbCredentialBox',
|
|
]
|
|
],
|
|
'help' => $this->parent->getHelpBox( 'config-mssql-install-auth' )
|
|
] ) .
|
|
Html::openElement( 'div', [ 'id' => 'dbCredentialBox', 'style' => $displayStyle ] ) .
|
|
$this->getTextBox(
|
|
'_InstallUser',
|
|
'config-db-username',
|
|
[ 'dir' => 'ltr' ],
|
|
$this->parent->getHelpBox( 'config-db-install-username' )
|
|
) .
|
|
$this->getPasswordBox(
|
|
'_InstallPassword',
|
|
'config-db-password',
|
|
[ 'dir' => 'ltr' ],
|
|
$this->parent->getHelpBox( 'config-db-install-password' )
|
|
) .
|
|
Html::closeElement( 'div' ) .
|
|
Html::closeElement( 'fieldset' );
|
|
}
|
|
|
|
public function submitConnectForm() {
|
|
// Get variables from the request.
|
|
$newValues = $this->setVarsFromRequest( [
|
|
'wgDBserver',
|
|
'wgDBname',
|
|
'wgDBmwschema',
|
|
'wgDBprefix'
|
|
] );
|
|
|
|
// Validate them.
|
|
$status = Status::newGood();
|
|
if ( !strlen( $newValues['wgDBserver'] ) ) {
|
|
$status->fatal( 'config-missing-db-host' );
|
|
}
|
|
if ( !strlen( $newValues['wgDBname'] ) ) {
|
|
$status->fatal( 'config-missing-db-name' );
|
|
} elseif ( !preg_match( '/^[a-z0-9_]+$/i', $newValues['wgDBname'] ) ) {
|
|
$status->fatal( 'config-invalid-db-name', $newValues['wgDBname'] );
|
|
}
|
|
if ( !preg_match( '/^[a-z0-9_]*$/i', $newValues['wgDBmwschema'] ) ) {
|
|
$status->fatal( 'config-invalid-schema', $newValues['wgDBmwschema'] );
|
|
}
|
|
if ( !preg_match( '/^[a-z0-9_]*$/i', $newValues['wgDBprefix'] ) ) {
|
|
$status->fatal( 'config-invalid-db-prefix', $newValues['wgDBprefix'] );
|
|
}
|
|
if ( !$status->isOK() ) {
|
|
return $status;
|
|
}
|
|
|
|
// Check for blank schema and remap to dbo
|
|
if ( $newValues['wgDBmwschema'] === '' ) {
|
|
$this->setVar( 'wgDBmwschema', 'dbo' );
|
|
}
|
|
|
|
// User box
|
|
$this->setVarsFromRequest( [
|
|
'_InstallUser',
|
|
'_InstallPassword',
|
|
'_InstallWindowsAuthentication'
|
|
] );
|
|
|
|
// Try to connect
|
|
$status = $this->getConnection();
|
|
if ( !$status->isOK() ) {
|
|
return $status;
|
|
}
|
|
/**
|
|
* @var $conn Database
|
|
*/
|
|
$conn = $status->value;
|
|
|
|
// Check version
|
|
return static::meetsMinimumRequirement( $conn->getServerVersion() );
|
|
}
|
|
|
|
/**
|
|
* @return Status
|
|
*/
|
|
public function openConnection() {
|
|
global $wgDBWindowsAuthentication;
|
|
$status = Status::newGood();
|
|
$user = $this->getVar( '_InstallUser' );
|
|
$password = $this->getVar( '_InstallPassword' );
|
|
|
|
if ( $this->getVar( '_InstallWindowsAuthentication' ) == 'windowsauth' ) {
|
|
// Use Windows authentication for this connection
|
|
$wgDBWindowsAuthentication = true;
|
|
} else {
|
|
$wgDBWindowsAuthentication = false;
|
|
}
|
|
|
|
try {
|
|
$db = Database::factory( 'mssql', [
|
|
'host' => $this->getVar( 'wgDBserver' ),
|
|
'port' => $this->getVar( 'wgDBport' ),
|
|
'user' => $user,
|
|
'password' => $password,
|
|
'dbname' => false,
|
|
'flags' => 0,
|
|
'schema' => $this->getVar( 'wgDBmwschema' ),
|
|
'tablePrefix' => $this->getVar( 'wgDBprefix' ) ] );
|
|
$db->prepareStatements( false );
|
|
$db->scrollableCursor( false );
|
|
$status->value = $db;
|
|
} catch ( DBConnectionError $e ) {
|
|
$status->fatal( 'config-connection-error', $e->getMessage() );
|
|
}
|
|
|
|
return $status;
|
|
}
|
|
|
|
public function preUpgrade() {
|
|
global $wgDBuser, $wgDBpassword;
|
|
|
|
$status = $this->getConnection();
|
|
if ( !$status->isOK() ) {
|
|
$this->parent->showStatusError( $status );
|
|
|
|
return;
|
|
}
|
|
/**
|
|
* @var $conn Database
|
|
*/
|
|
$conn = $status->value;
|
|
$conn->selectDB( $this->getVar( 'wgDBname' ) );
|
|
|
|
# Normal user and password are selected after this step, so for now
|
|
# just copy these two
|
|
$wgDBuser = $this->getVar( '_InstallUser' );
|
|
$wgDBpassword = $this->getVar( '_InstallPassword' );
|
|
}
|
|
|
|
/**
|
|
* Return true if the install user can create accounts
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function canCreateAccounts() {
|
|
$status = $this->getConnection();
|
|
if ( !$status->isOK() ) {
|
|
return false;
|
|
}
|
|
/** @var Database $conn */
|
|
$conn = $status->value;
|
|
|
|
// We need the server-level ALTER ANY LOGIN permission to create new accounts
|
|
$res = $conn->query( "SELECT permission_name FROM sys.fn_my_permissions( NULL, 'SERVER' )" );
|
|
$serverPrivs = [
|
|
'ALTER ANY LOGIN' => false,
|
|
'CONTROL SERVER' => false,
|
|
];
|
|
|
|
foreach ( $res as $row ) {
|
|
$serverPrivs[$row->permission_name] = true;
|
|
}
|
|
|
|
if ( !$serverPrivs['ALTER ANY LOGIN'] ) {
|
|
return false;
|
|
}
|
|
|
|
// Check to ensure we can grant everything needed as well
|
|
// We can't actually tell if we have WITH GRANT OPTION for a given permission, so we assume we do
|
|
// and just check for the permission
|
|
// https://technet.microsoft.com/en-us/library/ms178569.aspx
|
|
// The following array sets up which permissions imply whatever permissions we specify
|
|
$implied = [
|
|
// schema database server
|
|
'DELETE' => [ 'DELETE', 'CONTROL SERVER' ],
|
|
'EXECUTE' => [ 'EXECUTE', 'CONTROL SERVER' ],
|
|
'INSERT' => [ 'INSERT', 'CONTROL SERVER' ],
|
|
'SELECT' => [ 'SELECT', 'CONTROL SERVER' ],
|
|
'UPDATE' => [ 'UPDATE', 'CONTROL SERVER' ],
|
|
];
|
|
|
|
$grantOptions = array_flip( $this->webUserPrivs );
|
|
|
|
// Check for schema and db-level permissions, but only if the schema/db exists
|
|
$schemaPrivs = $dbPrivs = [
|
|
'DELETE' => false,
|
|
'EXECUTE' => false,
|
|
'INSERT' => false,
|
|
'SELECT' => false,
|
|
'UPDATE' => false,
|
|
];
|
|
|
|
$dbPrivs['ALTER ANY USER'] = false;
|
|
|
|
if ( $this->databaseExists( $this->getVar( 'wgDBname' ) ) ) {
|
|
$conn->selectDB( $this->getVar( 'wgDBname' ) );
|
|
$res = $conn->query( "SELECT permission_name FROM sys.fn_my_permissions( NULL, 'DATABASE' )" );
|
|
|
|
foreach ( $res as $row ) {
|
|
$dbPrivs[$row->permission_name] = true;
|
|
}
|
|
|
|
// If the db exists, we need ALTER ANY USER privs on it to make a new user
|
|
if ( !$dbPrivs['ALTER ANY USER'] ) {
|
|
return false;
|
|
}
|
|
|
|
if ( $this->schemaExists( $this->getVar( 'wgDBmwschema' ) ) ) {
|
|
// wgDBmwschema is validated to only contain alphanumeric + underscore, so this is safe
|
|
$res = $conn->query( "SELECT permission_name FROM sys.fn_my_permissions( "
|
|
. "'{$this->getVar( 'wgDBmwschema' )}', 'SCHEMA' )" );
|
|
|
|
foreach ( $res as $row ) {
|
|
$schemaPrivs[$row->permission_name] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now check all the grants we'll need to be doing to see if we can
|
|
foreach ( $this->webUserPrivs as $permission ) {
|
|
if ( ( isset( $schemaPrivs[$permission] ) && $schemaPrivs[$permission] )
|
|
|| ( isset( $dbPrivs[$implied[$permission][0]] )
|
|
&& $dbPrivs[$implied[$permission][0]] )
|
|
|| ( isset( $serverPrivs[$implied[$permission][1]] )
|
|
&& $serverPrivs[$implied[$permission][1]] )
|
|
) {
|
|
unset( $grantOptions[$permission] );
|
|
}
|
|
}
|
|
|
|
if ( count( $grantOptions ) ) {
|
|
// Can't grant everything
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getSettingsForm() {
|
|
if ( $this->canCreateAccounts() ) {
|
|
$noCreateMsg = false;
|
|
} else {
|
|
$noCreateMsg = 'config-db-web-no-create-privs';
|
|
}
|
|
|
|
$wrapperStyle = $this->getVar( '_SameAccount' ) ? 'display: none' : '';
|
|
$displayStyle = $this->getVar( '_WebWindowsAuthentication' ) == 'windowsauth'
|
|
? 'display: none'
|
|
: '';
|
|
$s = Html::openElement( 'fieldset' ) .
|
|
Html::element( 'legend', [], wfMessage( 'config-db-web-account' )->text() ) .
|
|
$this->getCheckBox(
|
|
'_SameAccount', 'config-db-web-account-same',
|
|
[ 'class' => 'hideShowRadio', 'rel' => 'dbOtherAccount' ]
|
|
) .
|
|
Html::openElement( 'div', [ 'id' => 'dbOtherAccount', 'style' => $wrapperStyle ] ) .
|
|
$this->getRadioSet( [
|
|
'var' => '_WebWindowsAuthentication',
|
|
'label' => 'config-mssql-auth',
|
|
'itemLabelPrefix' => 'config-mssql-',
|
|
'values' => [ 'sqlauth', 'windowsauth' ],
|
|
'itemAttribs' => [
|
|
'sqlauth' => [
|
|
'class' => 'showHideRadio',
|
|
'rel' => 'dbCredentialBox',
|
|
],
|
|
'windowsauth' => [
|
|
'class' => 'hideShowRadio',
|
|
'rel' => 'dbCredentialBox',
|
|
]
|
|
],
|
|
'help' => $this->parent->getHelpBox( 'config-mssql-web-auth' )
|
|
] ) .
|
|
Html::openElement( 'div', [ 'id' => 'dbCredentialBox', 'style' => $displayStyle ] ) .
|
|
$this->getTextBox( 'wgDBuser', 'config-db-username' ) .
|
|
$this->getPasswordBox( 'wgDBpassword', 'config-db-password' ) .
|
|
Html::closeElement( 'div' );
|
|
|
|
if ( $noCreateMsg ) {
|
|
$s .= $this->parent->getWarningBox( wfMessage( $noCreateMsg )->plain() );
|
|
} else {
|
|
$s .= $this->getCheckBox( '_CreateDBAccount', 'config-db-web-create' );
|
|
}
|
|
|
|
$s .= Html::closeElement( 'div' ) . Html::closeElement( 'fieldset' );
|
|
|
|
return $s;
|
|
}
|
|
|
|
/**
|
|
* @return Status
|
|
*/
|
|
public function submitSettingsForm() {
|
|
$this->setVarsFromRequest( [
|
|
'wgDBuser',
|
|
'wgDBpassword',
|
|
'_SameAccount',
|
|
'_CreateDBAccount',
|
|
'_WebWindowsAuthentication'
|
|
] );
|
|
|
|
if ( $this->getVar( '_SameAccount' ) ) {
|
|
$this->setVar( '_WebWindowsAuthentication', $this->getVar( '_InstallWindowsAuthentication' ) );
|
|
$this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
|
|
$this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
|
|
}
|
|
|
|
if ( $this->getVar( '_WebWindowsAuthentication' ) == 'windowsauth' ) {
|
|
$this->setVar( 'wgDBuser', '' );
|
|
$this->setVar( 'wgDBpassword', '' );
|
|
$this->setVar( 'wgDBWindowsAuthentication', true );
|
|
} else {
|
|
$this->setVar( 'wgDBWindowsAuthentication', false );
|
|
}
|
|
|
|
if ( $this->getVar( '_CreateDBAccount' )
|
|
&& $this->getVar( '_WebWindowsAuthentication' ) == 'sqlauth'
|
|
&& strval( $this->getVar( 'wgDBpassword' ) ) == ''
|
|
) {
|
|
return Status::newFatal( 'config-db-password-empty', $this->getVar( 'wgDBuser' ) );
|
|
}
|
|
|
|
// Validate the create checkbox
|
|
$canCreate = $this->canCreateAccounts();
|
|
if ( !$canCreate ) {
|
|
$this->setVar( '_CreateDBAccount', false );
|
|
$create = false;
|
|
} else {
|
|
$create = $this->getVar( '_CreateDBAccount' );
|
|
}
|
|
|
|
if ( !$create ) {
|
|
// Test the web account
|
|
$user = $this->getVar( 'wgDBuser' );
|
|
$password = $this->getVar( 'wgDBpassword' );
|
|
|
|
if ( $this->getVar( '_WebWindowsAuthentication' ) == 'windowsauth' ) {
|
|
$user = 'windowsauth';
|
|
$password = 'windowsauth';
|
|
}
|
|
|
|
try {
|
|
Database::factory( 'mssql', [
|
|
'host' => $this->getVar( 'wgDBserver' ),
|
|
'user' => $user,
|
|
'password' => $password,
|
|
'dbname' => false,
|
|
'flags' => 0,
|
|
'tablePrefix' => $this->getVar( 'wgDBprefix' ),
|
|
'schema' => $this->getVar( 'wgDBmwschema' ),
|
|
] );
|
|
} catch ( DBConnectionError $e ) {
|
|
return Status::newFatal( 'config-connection-error', $e->getMessage() );
|
|
}
|
|
}
|
|
|
|
return Status::newGood();
|
|
}
|
|
|
|
public function preInstall() {
|
|
# Add our user callback to installSteps, right before the tables are created.
|
|
$callback = [
|
|
'name' => 'user',
|
|
'callback' => [ $this, 'setupUser' ],
|
|
];
|
|
$this->parent->addInstallStep( $callback, 'tables' );
|
|
}
|
|
|
|
/**
|
|
* @return Status
|
|
*/
|
|
public function setupDatabase() {
|
|
$status = $this->getConnection();
|
|
if ( !$status->isOK() ) {
|
|
return $status;
|
|
}
|
|
/** @var Database $conn */
|
|
$conn = $status->value;
|
|
$dbName = $this->getVar( 'wgDBname' );
|
|
$schemaName = $this->getVar( 'wgDBmwschema' );
|
|
if ( !$this->databaseExists( $dbName ) ) {
|
|
$conn->query(
|
|
"CREATE DATABASE " . $conn->addIdentifierQuotes( $dbName ),
|
|
__METHOD__
|
|
);
|
|
}
|
|
$conn->selectDB( $dbName );
|
|
if ( !$this->schemaExists( $schemaName ) ) {
|
|
$conn->query(
|
|
"CREATE SCHEMA " . $conn->addIdentifierQuotes( $schemaName ),
|
|
__METHOD__
|
|
);
|
|
}
|
|
if ( !$this->catalogExists( $schemaName ) ) {
|
|
$conn->query(
|
|
"CREATE FULLTEXT CATALOG " . $conn->addIdentifierQuotes( $schemaName ),
|
|
__METHOD__
|
|
);
|
|
}
|
|
$this->setupSchemaVars();
|
|
|
|
return $status;
|
|
}
|
|
|
|
/**
|
|
* @return Status
|
|
*/
|
|
public function setupUser() {
|
|
$dbUser = $this->getVar( 'wgDBuser' );
|
|
if ( $dbUser == $this->getVar( '_InstallUser' )
|
|
|| ( $this->getVar( '_InstallWindowsAuthentication' ) == 'windowsauth'
|
|
&& $this->getVar( '_WebWindowsAuthentication' ) == 'windowsauth' ) ) {
|
|
return Status::newGood();
|
|
}
|
|
$status = $this->getConnection();
|
|
if ( !$status->isOK() ) {
|
|
return $status;
|
|
}
|
|
|
|
$this->setupSchemaVars();
|
|
$dbName = $this->getVar( 'wgDBname' );
|
|
$this->db->selectDB( $dbName );
|
|
$password = $this->getVar( 'wgDBpassword' );
|
|
$schemaName = $this->getVar( 'wgDBmwschema' );
|
|
|
|
if ( $this->getVar( '_WebWindowsAuthentication' ) == 'windowsauth' ) {
|
|
$dbUser = 'windowsauth';
|
|
$password = 'windowsauth';
|
|
}
|
|
|
|
if ( $this->getVar( '_CreateDBAccount' ) ) {
|
|
$tryToCreate = true;
|
|
} else {
|
|
$tryToCreate = false;
|
|
}
|
|
|
|
$escUser = $this->db->addIdentifierQuotes( $dbUser );
|
|
$escDb = $this->db->addIdentifierQuotes( $dbName );
|
|
$escSchema = $this->db->addIdentifierQuotes( $schemaName );
|
|
$grantableNames = [];
|
|
if ( $tryToCreate ) {
|
|
$escPass = $this->db->addQuotes( $password );
|
|
|
|
if ( !$this->loginExists( $dbUser ) ) {
|
|
try {
|
|
$this->db->begin();
|
|
$this->db->selectDB( 'master' );
|
|
$logintype = $this->getVar( '_WebWindowsAuthentication' ) == 'windowsauth'
|
|
? 'FROM WINDOWS'
|
|
: "WITH PASSWORD = $escPass";
|
|
$this->db->query( "CREATE LOGIN $escUser $logintype" );
|
|
$this->db->selectDB( $dbName );
|
|
$this->db->query( "CREATE USER $escUser FOR LOGIN $escUser WITH DEFAULT_SCHEMA = $escSchema" );
|
|
$this->db->commit();
|
|
$grantableNames[] = $dbUser;
|
|
} catch ( DBQueryError $dqe ) {
|
|
$this->db->rollback();
|
|
$status->warning( 'config-install-user-create-failed', $dbUser, $dqe->getMessage() );
|
|
}
|
|
} elseif ( !$this->userExists( $dbUser ) ) {
|
|
try {
|
|
$this->db->begin();
|
|
$this->db->selectDB( $dbName );
|
|
$this->db->query( "CREATE USER $escUser FOR LOGIN $escUser WITH DEFAULT_SCHEMA = $escSchema" );
|
|
$this->db->commit();
|
|
$grantableNames[] = $dbUser;
|
|
} catch ( DBQueryError $dqe ) {
|
|
$this->db->rollback();
|
|
$status->warning( 'config-install-user-create-failed', $dbUser, $dqe->getMessage() );
|
|
}
|
|
} else {
|
|
$status->warning( 'config-install-user-alreadyexists', $dbUser );
|
|
$grantableNames[] = $dbUser;
|
|
}
|
|
}
|
|
|
|
// Try to grant to all the users we know exist or we were able to create
|
|
$this->db->selectDB( $dbName );
|
|
foreach ( $grantableNames as $name ) {
|
|
try {
|
|
// First try to grant full permissions
|
|
$fullPrivArr = [
|
|
'BACKUP DATABASE', 'BACKUP LOG', 'CREATE FUNCTION', 'CREATE PROCEDURE',
|
|
'CREATE TABLE', 'CREATE VIEW', 'CREATE FULLTEXT CATALOG', 'SHOWPLAN'
|
|
];
|
|
$fullPrivList = implode( ', ', $fullPrivArr );
|
|
$this->db->begin();
|
|
$this->db->query( "GRANT $fullPrivList ON DATABASE :: $escDb TO $escUser", __METHOD__ );
|
|
$this->db->query( "GRANT CONTROL ON SCHEMA :: $escSchema TO $escUser", __METHOD__ );
|
|
$this->db->commit();
|
|
} catch ( DBQueryError $dqe ) {
|
|
// If that fails, try to grant the limited subset specified in $this->webUserPrivs
|
|
try {
|
|
$privList = implode( ', ', $this->webUserPrivs );
|
|
$this->db->rollback();
|
|
$this->db->begin();
|
|
$this->db->query( "GRANT $privList ON SCHEMA :: $escSchema TO $escUser", __METHOD__ );
|
|
$this->db->commit();
|
|
} catch ( DBQueryError $dqe ) {
|
|
$this->db->rollback();
|
|
$status->fatal( 'config-install-user-grant-failed', $dbUser, $dqe->getMessage() );
|
|
}
|
|
// Also try to grant SHOWPLAN on the db, but don't fail if we can't
|
|
// (just makes a couple things in mediawiki run slower since
|
|
// we have to run SELECT COUNT(*) instead of getting the query plan)
|
|
try {
|
|
$this->db->query( "GRANT SHOWPLAN ON DATABASE :: $escDb TO $escUser", __METHOD__ );
|
|
} catch ( DBQueryError $dqe ) {
|
|
}
|
|
}
|
|
}
|
|
|
|
return $status;
|
|
}
|
|
|
|
public function createTables() {
|
|
$status = parent::createTables();
|
|
|
|
// Do last-minute stuff like fulltext indexes (since they can't be inside a transaction)
|
|
if ( $status->isOK() ) {
|
|
$searchindex = $this->db->tableName( 'searchindex' );
|
|
$schema = $this->db->addIdentifierQuotes( $this->getVar( 'wgDBmwschema' ) );
|
|
try {
|
|
$this->db->query( "CREATE FULLTEXT INDEX ON $searchindex (si_title, si_text) "
|
|
. "KEY INDEX si_page ON $schema" );
|
|
} catch ( DBQueryError $dqe ) {
|
|
$status->fatal( 'config-install-tables-failed', $dqe->getMessage() );
|
|
}
|
|
}
|
|
|
|
return $status;
|
|
}
|
|
|
|
public function getGlobalDefaults() {
|
|
// The default $wgDBmwschema is null, which breaks Postgres and other DBMSes that require
|
|
// the use of a schema, so we need to set it here
|
|
return array_merge( parent::getGlobalDefaults(), [
|
|
'wgDBmwschema' => 'mediawiki',
|
|
] );
|
|
}
|
|
|
|
/**
|
|
* Try to see if the login exists
|
|
* @param string $user Username to check
|
|
* @return bool
|
|
*/
|
|
private function loginExists( $user ) {
|
|
$res = $this->db->selectField( 'sys.sql_logins', 1, [ 'name' => $user ] );
|
|
return (bool)$res;
|
|
}
|
|
|
|
/**
|
|
* Try to see if the user account exists
|
|
* We assume we already have the appropriate database selected
|
|
* @param string $user Username to check
|
|
* @return bool
|
|
*/
|
|
private function userExists( $user ) {
|
|
$res = $this->db->selectField( 'sys.sysusers', 1, [ 'name' => $user ] );
|
|
return (bool)$res;
|
|
}
|
|
|
|
/**
|
|
* Try to see if a given database exists
|
|
* @param string $dbName Database name to check
|
|
* @return bool
|
|
*/
|
|
private function databaseExists( $dbName ) {
|
|
$res = $this->db->selectField( 'sys.databases', 1, [ 'name' => $dbName ] );
|
|
return (bool)$res;
|
|
}
|
|
|
|
/**
|
|
* Try to see if a given schema exists
|
|
* We assume we already have the appropriate database selected
|
|
* @param string $schemaName Schema name to check
|
|
* @return bool
|
|
*/
|
|
private function schemaExists( $schemaName ) {
|
|
$res = $this->db->selectField( 'sys.schemas', 1, [ 'name' => $schemaName ] );
|
|
return (bool)$res;
|
|
}
|
|
|
|
/**
|
|
* Try to see if a given fulltext catalog exists
|
|
* We assume we already have the appropriate database selected
|
|
* @param string $catalogName Catalog name to check
|
|
* @return bool
|
|
*/
|
|
private function catalogExists( $catalogName ) {
|
|
$res = $this->db->selectField( 'sys.fulltext_catalogs', 1, [ 'name' => $catalogName ] );
|
|
return (bool)$res;
|
|
}
|
|
|
|
/**
|
|
* Get variables to substitute into tables.sql and the SQL patch files.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getSchemaVars() {
|
|
return [
|
|
'wgDBname' => $this->getVar( 'wgDBname' ),
|
|
'wgDBmwschema' => $this->getVar( 'wgDBmwschema' ),
|
|
'wgDBuser' => $this->getVar( 'wgDBuser' ),
|
|
'wgDBpassword' => $this->getVar( 'wgDBpassword' ),
|
|
];
|
|
}
|
|
|
|
public function getLocalSettings() {
|
|
$schema = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgDBmwschema' ) );
|
|
$prefix = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgDBprefix' ) );
|
|
$windowsauth = $this->getVar( 'wgDBWindowsAuthentication' ) ? 'true' : 'false';
|
|
|
|
return "# MSSQL specific settings
|
|
\$wgDBWindowsAuthentication = {$windowsauth};
|
|
\$wgDBmwschema = \"{$schema}\";
|
|
\$wgDBprefix = \"{$prefix}\";";
|
|
}
|
|
}
|