Split web-specific code out of DatabaseInstaller

DatabaseInstaller had some LSP violations, such as calling undeclared
methods of the supplied Installer object, assuming that it was a
WebInstaller. It was also large.

So, split the web form parts of DatabaseInstaller into separate classes.
We have a class hierarchy for the connect forms, and a class hierarchy
for the settings forms, with a base class DatabaseForm mostly as a place
to put protected helper methods.

Also, have DatabaseInstaller::getConnection() return a special subclass
of Status, so that we can remove many Phan type overrides.

Change-Id: Ie84025f8f70b895fa6882848b9a21ba1750d60e2
This commit is contained in:
Tim Starling 2024-03-04 16:27:34 +11:00
parent 2d9190700d
commit 3f852f7ddc
17 changed files with 832 additions and 644 deletions

View file

@ -1549,7 +1549,11 @@ $wgAutoloadLocalClasses = [
'MediaWiki\\Http\\MwHttpRequestToResponseInterfaceAdapter' => __DIR__ . '/includes/http/MwHttpRequestToResponseInterfaceAdapter.php',
'MediaWiki\\Http\\Telemetry' => __DIR__ . '/includes/http/Telemetry.php',
'MediaWiki\\Installer\\CliInstaller' => __DIR__ . '/includes/installer/CliInstaller.php',
'MediaWiki\\Installer\\ConnectionStatus' => __DIR__ . '/includes/installer/ConnectionStatus.php',
'MediaWiki\\Installer\\DatabaseConnectForm' => __DIR__ . '/includes/installer/DatabaseConnectForm.php',
'MediaWiki\\Installer\\DatabaseForm' => __DIR__ . '/includes/installer/DatabaseForm.php',
'MediaWiki\\Installer\\DatabaseInstaller' => __DIR__ . '/includes/installer/DatabaseInstaller.php',
'MediaWiki\\Installer\\DatabaseSettingsForm' => __DIR__ . '/includes/installer/DatabaseSettingsForm.php',
'MediaWiki\\Installer\\DatabaseUpdater' => __DIR__ . '/includes/installer/DatabaseUpdater.php',
'MediaWiki\\Installer\\Hook\\LoadExtensionSchemaUpdatesHook' => __DIR__ . '/includes/installer/Hook/LoadExtensionSchemaUpdatesHook.php',
'MediaWiki\\Installer\\InstallDocFormatter' => __DIR__ . '/includes/installer/InstallDocFormatter.php',
@ -1558,11 +1562,16 @@ $wgAutoloadLocalClasses = [
'MediaWiki\\Installer\\InstallerOverrides' => __DIR__ . '/includes/installer/InstallerOverrides.php',
'MediaWiki\\Installer\\InstallerSessionProvider' => __DIR__ . '/includes/installer/InstallerSessionProvider.php',
'MediaWiki\\Installer\\LocalSettingsGenerator' => __DIR__ . '/includes/installer/LocalSettingsGenerator.php',
'MediaWiki\\Installer\\MysqlConnectForm' => __DIR__ . '/includes/installer/MysqlConnectForm.php',
'MediaWiki\\Installer\\MysqlInstaller' => __DIR__ . '/includes/installer/MysqlInstaller.php',
'MediaWiki\\Installer\\MysqlSettingsForm' => __DIR__ . '/includes/installer/MysqlSettingsForm.php',
'MediaWiki\\Installer\\MysqlUpdater' => __DIR__ . '/includes/installer/MysqlUpdater.php',
'MediaWiki\\Installer\\Pingback' => __DIR__ . '/includes/installer/Pingback.php',
'MediaWiki\\Installer\\PostgresConnectForm' => __DIR__ . '/includes/installer/PostgresConnectForm.php',
'MediaWiki\\Installer\\PostgresInstaller' => __DIR__ . '/includes/installer/PostgresInstaller.php',
'MediaWiki\\Installer\\PostgresSettingsForm' => __DIR__ . '/includes/installer/PostgresSettingsForm.php',
'MediaWiki\\Installer\\PostgresUpdater' => __DIR__ . '/includes/installer/PostgresUpdater.php',
'MediaWiki\\Installer\\SqliteConnectForm' => __DIR__ . '/includes/installer/SqliteConnectForm.php',
'MediaWiki\\Installer\\SqliteInstaller' => __DIR__ . '/includes/installer/SqliteInstaller.php',
'MediaWiki\\Installer\\SqliteUpdater' => __DIR__ . '/includes/installer/SqliteUpdater.php',
'MediaWiki\\Installer\\WebInstaller' => __DIR__ . '/includes/installer/WebInstaller.php',

View file

@ -0,0 +1,23 @@
<?php
namespace MediaWiki\Installer;
use Status;
use Wikimedia\Rdbms\Database;
/**
* @internal
*/
class ConnectionStatus extends Status {
public function __construct( Database $db = null ) {
$this->value = $db;
}
public function setDB( Database $db ) {
$this->value = $db;
}
public function getDB(): Database {
return $this->value;
}
}

View file

@ -0,0 +1,71 @@
<?php
namespace MediaWiki\Installer;
use MediaWiki\Html\Html;
use MediaWiki\Status\Status;
/**
* @internal
*/
abstract class DatabaseConnectForm extends DatabaseForm {
/**
* Get HTML for a web form that configures this database. Configuration
* at this time should be the minimum needed to connect and test
* whether install or upgrade is required.
*
* If this is called, $this->parent can be assumed to be a WebInstaller.
*/
abstract public function getHtml();
/**
* Set variables based on the request array, assuming it was submitted
* via the form returned by getConnectForm(). Validate the connection
* settings by attempting to connect with them.
*
* If this is called, $this->parent can be assumed to be a WebInstaller.
*
* @return Status
*/
abstract public function submit();
/**
* Get a standard install-user fieldset.
*
* @return string
*/
protected function getInstallUserBox() {
return "<span class=\"cdx-card\"><span class=\"cdx-card__text\">" .
Html::element(
'span',
[ 'class' => 'cdx-card__text__title' ],
wfMessage( 'config-db-install-account' )->text()
) .
"<span class=\"cdx-card__text__description\">" .
$this->getTextBox(
'_InstallUser',
'config-db-username',
[ 'dir' => 'ltr' ],
$this->webInstaller->getHelpBox( 'config-db-install-username' )
) .
$this->getPasswordBox(
'_InstallPassword',
'config-db-password',
[ 'dir' => 'ltr' ],
$this->webInstaller->getHelpBox( 'config-db-install-password' )
) .
"</span></span></span>";
}
/**
* Submit a standard install user fieldset.
* @return Status
*/
protected function submitInstallUserBox() {
$this->setVarsFromRequest( [ '_InstallUser', '_InstallPassword' ] );
return Status::newGood();
}
}

View file

@ -0,0 +1,152 @@
<?php
namespace MediaWiki\Installer;
/**
* @internal
*/
abstract class DatabaseForm {
protected WebInstaller $webInstaller;
protected DatabaseInstaller $dbInstaller;
public function __construct( WebInstaller $webInstaller, DatabaseInstaller $dbInstaller ) {
$this->webInstaller = $webInstaller;
$this->dbInstaller = $dbInstaller;
}
/**
* Get a variable, taking local defaults into account.
* @param string $var
* @param mixed|null $default
* @return mixed
*/
protected function getVar( $var, $default = null ) {
return $this->dbInstaller->getVar( $var, $default );
}
/**
* Set a variable
* @param string $name
* @param mixed $value
*/
protected function setVar( $name, $value ) {
$this->dbInstaller->setVar( $name, $value );
}
/**
* Return the internal name, e.g. 'mysql', or 'sqlite'.
* @return string
*/
protected function getName() {
return $this->dbInstaller->getName();
}
/**
* Get a labelled text box to configure a local variable.
*
* @param string $var
* @param string $label
* @param array $attribs
* @param string $helpData HTML
* @return string HTML
* @return-taint escaped
*/
protected function getTextBox( $var, $label, $attribs = [], $helpData = "" ) {
$name = $this->getName() . '_' . $var;
$value = $this->getVar( $var );
if ( !isset( $attribs ) ) {
$attribs = [];
}
return $this->webInstaller->getTextBox( [
'var' => $var,
'label' => $label,
'attribs' => $attribs,
'controlName' => $name,
'value' => $value,
'help' => $helpData
] );
}
/**
* Get a labelled password box to configure a local variable.
* Implements password hiding.
*
* @param string $var
* @param string $label
* @param array $attribs
* @param string $helpData HTML
* @return string HTML
* @return-taint escaped
*/
protected function getPasswordBox( $var, $label, $attribs = [], $helpData = "" ) {
$name = $this->getName() . '_' . $var;
$value = $this->getVar( $var );
if ( !isset( $attribs ) ) {
$attribs = [];
}
return $this->webInstaller->getPasswordBox( [
'var' => $var,
'label' => $label,
'attribs' => $attribs,
'controlName' => $name,
'value' => $value,
'help' => $helpData
] );
}
/**
* Get a labelled checkbox to configure a local boolean variable.
*
* @param string $var
* @param string $label
* @param array $attribs Optional.
* @param string $helpData Optional.
* @return string
*/
protected function getCheckBox( $var, $label, $attribs = [], $helpData = "" ) {
$name = $this->getName() . '_' . $var;
$value = $this->getVar( $var );
return $this->webInstaller->getCheckBox( [
'var' => $var,
'label' => $label,
'attribs' => $attribs,
'controlName' => $name,
'value' => $value,
'help' => $helpData
] );
}
/**
* Get a set of labelled radio buttons.
*
* @param array $params Parameters are:
* var: The variable to be configured (required)
* label: The message name for the label (required)
* itemLabelPrefix: The message name prefix for the item labels (required)
* values: List of allowed values (required)
* itemAttribs Array of attribute arrays, outer key is the value name (optional)
*
* @return string
*/
protected function getRadioSet( $params ) {
$params['controlName'] = $this->getName() . '_' . $params['var'];
$params['value'] = $this->getVar( $params['var'] );
return $this->webInstaller->getRadioSet( $params );
}
/**
* Convenience function to set variables based on form data.
* Assumes that variables containing "password" in the name are (potentially
* fake) passwords.
* @param array $varNames
* @return array
*/
protected function setVarsFromRequest( $varNames ) {
return $this->webInstaller->setVarsFromRequest( $varNames, $this->getName() . '_' );
}
}

View file

@ -25,7 +25,6 @@
namespace MediaWiki\Installer;
use Exception;
use MediaWiki\Html\Html;
use MediaWiki\Status\Status;
use MWException;
use MWLBFactory;
@ -49,7 +48,7 @@ abstract class DatabaseInstaller {
/**
* The Installer object.
*
* @var WebInstaller
* @var Installer
*/
public $parent;
@ -121,54 +120,13 @@ abstract class DatabaseInstaller {
return Status::newGood();
}
/**
* Get HTML for a web form that configures this database. Configuration
* at this time should be the minimum needed to connect and test
* whether install or upgrade is required.
*
* If this is called, $this->parent can be assumed to be a WebInstaller.
*/
abstract public function getConnectForm();
/**
* Set variables based on the request array, assuming it was submitted
* via the form returned by getConnectForm(). Validate the connection
* settings by attempting to connect with them.
*
* If this is called, $this->parent can be assumed to be a WebInstaller.
*
* @return Status
*/
abstract public function submitConnectForm();
/**
* Get HTML for a web form that retrieves settings used for installation.
* $this->parent can be assumed to be a WebInstaller.
* If the DB type has no settings beyond those already configured with
* getConnectForm(), this should return false.
* @return string|false
*/
public function getSettingsForm() {
return false;
}
/**
* Set variables based on the request array, assuming it was submitted via
* the form return by getSettingsForm().
*
* @return Status
*/
public function submitSettingsForm() {
return Status::newGood();
}
/**
* Open a connection to the database using the administrative user/password
* currently defined in the session, without any caching. Returns a status
* object. On success, the status object will contain a Database object in
* its value member.
*
* @return Status
* @return ConnectionStatus
*/
abstract public function openConnection();
@ -187,12 +145,11 @@ abstract class DatabaseInstaller {
*
* This will return a cached connection if one is available.
*
* @return Status
* @suppress PhanUndeclaredMethod
* @return ConnectionStatus
*/
public function getConnection() {
if ( $this->db ) {
return Status::newGood( $this->db );
return new ConnectionStatus( $this->db );
}
$status = $this->openConnection();
@ -375,8 +332,7 @@ abstract class DatabaseInstaller {
public function setupSchemaVars() {
$status = $this->getConnection();
if ( $status->isOK() ) {
// @phan-suppress-next-line PhanUndeclaredMethod
$status->value->setSchemaVars( $this->getSchemaVars() );
$status->getDB()->setSchemaVars( $this->getSchemaVars() );
} else {
$msg = __METHOD__ . ': unexpected error while establishing'
. ' a database connection with message: '
@ -543,113 +499,9 @@ abstract class DatabaseInstaller {
$this->parent->setVar( $name, $value );
}
/**
* Get a labelled text box to configure a local variable.
*
* @param string $var
* @param string $label
* @param array $attribs
* @param string $helpData HTML
* @return string HTML
* @return-taint escaped
*/
public function getTextBox( $var, $label, $attribs = [], $helpData = "" ) {
$name = $this->getName() . '_' . $var;
$value = $this->getVar( $var );
if ( !isset( $attribs ) ) {
$attribs = [];
}
abstract public function getConnectForm( WebInstaller $webInstaller ): DatabaseConnectForm;
return $this->parent->getTextBox( [
'var' => $var,
'label' => $label,
'attribs' => $attribs,
'controlName' => $name,
'value' => $value,
'help' => $helpData
] );
}
/**
* Get a labelled password box to configure a local variable.
* Implements password hiding.
*
* @param string $var
* @param string $label
* @param array $attribs
* @param string $helpData HTML
* @return string HTML
* @return-taint escaped
*/
public function getPasswordBox( $var, $label, $attribs = [], $helpData = "" ) {
$name = $this->getName() . '_' . $var;
$value = $this->getVar( $var );
if ( !isset( $attribs ) ) {
$attribs = [];
}
return $this->parent->getPasswordBox( [
'var' => $var,
'label' => $label,
'attribs' => $attribs,
'controlName' => $name,
'value' => $value,
'help' => $helpData
] );
}
/**
* Get a labelled checkbox to configure a local boolean variable.
*
* @param string $var
* @param string $label
* @param array $attribs Optional.
* @param string $helpData Optional.
* @return string
*/
public function getCheckBox( $var, $label, $attribs = [], $helpData = "" ) {
$name = $this->getName() . '_' . $var;
$value = $this->getVar( $var );
return $this->parent->getCheckBox( [
'var' => $var,
'label' => $label,
'attribs' => $attribs,
'controlName' => $name,
'value' => $value,
'help' => $helpData
] );
}
/**
* Get a set of labelled radio buttons.
*
* @param array $params Parameters are:
* var: The variable to be configured (required)
* label: The message name for the label (required)
* itemLabelPrefix: The message name prefix for the item labels (required)
* values: List of allowed values (required)
* itemAttribs Array of attribute arrays, outer key is the value name (optional)
*
* @return string
*/
public function getRadioSet( $params ) {
$params['controlName'] = $this->getName() . '_' . $params['var'];
$params['value'] = $this->getVar( $params['var'] );
return $this->parent->getRadioSet( $params );
}
/**
* Convenience function to set variables based on form data.
* Assumes that variables containing "password" in the name are (potentially
* fake) passwords.
* @param array $varNames
* @return array
*/
public function setVarsFromRequest( $varNames ) {
return $this->parent->setVarsFromRequest( $varNames, $this->getName() . '_' );
}
abstract public function getSettingsForm( WebInstaller $webInstaller ): DatabaseSettingsForm;
/**
* Determine whether an existing installation of MediaWiki is present in
@ -680,99 +532,6 @@ abstract class DatabaseInstaller {
$this->db->tableExists( 'revision', __METHOD__ );
}
/**
* Get a standard install-user fieldset.
*
* @return string
*/
public function getInstallUserBox() {
return "<span class=\"cdx-card\"><span class=\"cdx-card__text\">" .
Html::element(
'span',
[ 'class' => 'cdx-card__text__title' ],
wfMessage( 'config-db-install-account' )->text()
) .
"<span class=\"cdx-card__text__description\">" .
$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' )
) .
"</span></span></span>";
}
/**
* Submit a standard install user fieldset.
* @return Status
*/
public function submitInstallUserBox() {
$this->setVarsFromRequest( [ '_InstallUser', '_InstallPassword' ] );
return Status::newGood();
}
/**
* Get a standard web-user fieldset
* @param string|false $noCreateMsg Message to display instead of the creation checkbox.
* Set this to false to show a creation checkbox (default).
*
* @return string
*/
public function getWebUserBox( $noCreateMsg = false ) {
$wrapperStyle = $this->getVar( '_SameAccount' ) ? 'display: none' : '';
$s = "<span class=\"cdx-card\"><span class=\"cdx-card__text\">" .
Html::element(
'span',
[ 'class' => 'cdx-card__text__title' ],
wfMessage( 'config-db-web-account' )->text()
) .
$this->getCheckBox(
'_SameAccount', 'config-db-web-account-same',
[ 'class' => 'hideShowRadio cdx-checkbox__input', 'rel' => 'dbOtherAccount' ]
) .
Html::openElement( 'div', [ 'id' => 'dbOtherAccount', 'style' => $wrapperStyle ] ) .
$this->getTextBox( 'wgDBuser', 'config-db-username' ) .
$this->getPasswordBox( 'wgDBpassword', 'config-db-password' ) .
$this->parent->getHelpBox( 'config-db-web-help' );
if ( $noCreateMsg ) {
$s .= Html::warningBox( wfMessage( $noCreateMsg )->plain(), 'config-warning-box' );
} else {
$s .= $this->getCheckBox( '_CreateDBAccount', 'config-db-web-create' );
}
$s .= Html::closeElement( 'div' ) . "</span></span></span>";
return $s;
}
/**
* Submit the form from getWebUserBox().
*
* @return Status
*/
public function submitWebUserBox() {
$this->setVarsFromRequest(
[ 'wgDBuser', 'wgDBpassword', '_SameAccount', '_CreateDBAccount' ]
);
if ( $this->getVar( '_SameAccount' ) ) {
$this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
$this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
}
if ( $this->getVar( '_CreateDBAccount' ) && strval( $this->getVar( 'wgDBpassword' ) ) == '' ) {
return Status::newFatal( 'config-db-password-empty', $this->getVar( 'wgDBuser' ) );
}
return Status::newGood();
}
/**
* Common function for databases that don't understand the MySQLish syntax of interwiki.list.
*

View file

@ -0,0 +1,89 @@
<?php
namespace MediaWiki\Installer;
use MediaWiki\Html\Html;
use MediaWiki\Status\Status;
/**
* @internal
*/
class DatabaseSettingsForm extends DatabaseForm {
/**
* Get HTML for a web form that retrieves settings used for installation.
* $this->parent can be assumed to be a WebInstaller.
* If the DB type has no settings beyond those already configured with
* getConnectForm(), this should return false.
* @return string|false
*/
public function getHtml() {
return false;
}
/**
* Set variables based on the request array, assuming it was submitted via
* the form return by getSettingsForm().
*
* @return Status
*/
public function submit() {
return Status::newGood();
}
/**
* Get a standard web-user fieldset
* @param string|false $noCreateMsg Message to display instead of the creation checkbox.
* Set this to false to show a creation checkbox (default).
*
* @return string
*/
protected function getWebUserBox( $noCreateMsg = false ) {
$wrapperStyle = $this->getVar( '_SameAccount' ) ? 'display: none' : '';
$s = "<span class=\"cdx-card\"><span class=\"cdx-card__text\">" .
Html::element(
'span',
[ 'class' => 'cdx-card__text__title' ],
wfMessage( 'config-db-web-account' )->text()
) .
$this->getCheckBox(
'_SameAccount', 'config-db-web-account-same',
[ 'class' => 'hideShowRadio cdx-checkbox__input', 'rel' => 'dbOtherAccount' ]
) .
Html::openElement( 'div', [ 'id' => 'dbOtherAccount', 'style' => $wrapperStyle ] ) .
$this->getTextBox( 'wgDBuser', 'config-db-username' ) .
$this->getPasswordBox( 'wgDBpassword', 'config-db-password' ) .
$this->webInstaller->getHelpBox( 'config-db-web-help' );
if ( $noCreateMsg ) {
$s .= Html::warningBox( wfMessage( $noCreateMsg )->plain(), 'config-warning-box' );
} else {
$s .= $this->getCheckBox( '_CreateDBAccount', 'config-db-web-create' );
}
$s .= Html::closeElement( 'div' ) . "</span></span></span>";
return $s;
}
/**
* Submit the form from getWebUserBox().
*
* @return Status
*/
public function submitWebUserBox() {
$this->setVarsFromRequest(
[ 'wgDBuser', 'wgDBpassword', '_SameAccount', '_CreateDBAccount' ]
);
if ( $this->getVar( '_SameAccount' ) ) {
$this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
$this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
}
if ( $this->getVar( '_CreateDBAccount' ) && strval( $this->getVar( 'wgDBpassword' ) ) == '' ) {
return Status::newFatal( 'config-db-password-empty', $this->getVar( 'wgDBuser' ) );
}
return Status::newGood();
}
}

View file

@ -848,8 +848,7 @@ abstract class Installer {
if ( !$status->isOK() ) {
return $status;
}
// @phan-suppress-next-line PhanUndeclaredMethod
$status->value->insert(
$status->getDB()->insert(
'site_stats',
[
'ss_row_id' => 1,

View file

@ -0,0 +1,74 @@
<?php
namespace MediaWiki\Installer;
use MediaWiki\Html\Html;
use MediaWiki\Status\Status;
/**
* @internal
*/
class MysqlConnectForm extends DatabaseConnectForm {
/**
* @return string
*/
public function getHtml() {
return $this->getTextBox(
'wgDBserver',
'config-db-host',
[],
$this->webInstaller->getHelpBox( 'config-db-host-help' )
) .
$this->getCheckBox( 'wgDBssl', 'config-db-ssl' ) .
"<span class=\"cdx-card\"><span class=\"cdx-card__text\">" .
Html::element(
'span',
[ 'class' => 'cdx-card__text__title' ],
wfMessage( 'config-db-wiki-settings' )->text()
) .
"<span class=\"cdx-card__text__description\">" .
$this->getTextBox( 'wgDBname', 'config-db-name', [ 'dir' => 'ltr' ],
$this->webInstaller->getHelpBox( 'config-db-name-help' ) ) .
$this->getTextBox( 'wgDBprefix', 'config-db-prefix', [ 'dir' => 'ltr' ],
$this->webInstaller->getHelpBox( 'config-db-prefix-help' ) ) .
"</span></span></span>" .
$this->getInstallUserBox();
}
public function submit() {
// Get variables from the request.
$newValues = $this->setVarsFromRequest( [ 'wgDBserver', 'wgDBname', 'wgDBprefix', 'wgDBssl' ] );
// 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['wgDBprefix'] ) ) {
$status->fatal( 'config-invalid-db-prefix', $newValues['wgDBprefix'] );
}
if ( !$status->isOK() ) {
return $status;
}
// Submit user box
$status = $this->submitInstallUserBox();
if ( !$status->isOK() ) {
return $status;
}
// Try to connect
$status = $this->dbInstaller->getConnection();
if ( !$status->isOK() ) {
return $status;
}
// Check version
return MysqlInstaller::meetsMinimumRequirement( $status->getDB() );
}
}

View file

@ -24,10 +24,7 @@
namespace MediaWiki\Installer;
use MediaWiki\Html\Html;
use MediaWiki\MediaWikiServices;
use MediaWiki\Status\Status;
use Wikimedia\Rdbms\Database;
use Wikimedia\Rdbms\DatabaseFactory;
use Wikimedia\Rdbms\DatabaseMySQL;
use Wikimedia\Rdbms\DBConnectionError;
@ -89,72 +86,12 @@ class MysqlInstaller extends DatabaseInstaller {
return self::checkExtension( 'mysqli' );
}
/**
* @return string
*/
public function getConnectForm() {
return $this->getTextBox(
'wgDBserver',
'config-db-host',
[],
$this->parent->getHelpBox( 'config-db-host-help' )
) .
$this->getCheckBox( 'wgDBssl', 'config-db-ssl' ) .
"<span class=\"cdx-card\"><span class=\"cdx-card__text\">" .
Html::element(
'span',
[ 'class' => 'cdx-card__text__title' ],
wfMessage( 'config-db-wiki-settings' )->text()
) .
"<span class=\"cdx-card__text__description\">" .
$this->getTextBox( 'wgDBname', 'config-db-name', [ 'dir' => 'ltr' ],
$this->parent->getHelpBox( 'config-db-name-help' ) ) .
$this->getTextBox( 'wgDBprefix', 'config-db-prefix', [ 'dir' => 'ltr' ],
$this->parent->getHelpBox( 'config-db-prefix-help' ) ) .
"</span></span></span>" .
$this->getInstallUserBox();
public function getConnectForm( WebInstaller $webInstaller ): DatabaseConnectForm {
return new MysqlConnectForm( $webInstaller, $this );
}
public function submitConnectForm() {
// Get variables from the request.
$newValues = $this->setVarsFromRequest( [ 'wgDBserver', 'wgDBname', 'wgDBprefix', 'wgDBssl' ] );
// 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['wgDBprefix'] ) ) {
$status->fatal( 'config-invalid-db-prefix', $newValues['wgDBprefix'] );
}
if ( !$status->isOK() ) {
return $status;
}
// Submit user box
$status = $this->submitInstallUserBox();
if ( !$status->isOK() ) {
return $status;
}
// Try to connect
$status = $this->getConnection();
if ( !$status->isOK() ) {
return $status;
}
/**
* @var Database $conn
*/
$conn = $status->value;
'@phan-var Database $conn';
// Check version
return static::meetsMinimumRequirement( $conn );
public function getSettingsForm( WebInstaller $webInstaller ): DatabaseSettingsForm {
return new MysqlSettingsForm( $webInstaller, $this );
}
public static function meetsMinimumRequirement( IDatabase $conn ) {
@ -166,10 +103,10 @@ class MysqlInstaller extends DatabaseInstaller {
}
/**
* @return Status
* @return ConnectionStatus
*/
public function openConnection() {
$status = Status::newGood();
$status = new ConnectionStatus;
try {
/** @var DatabaseMySQL $db */
$db = ( new DatabaseFactory() )->create( 'mysql', [
@ -180,7 +117,7 @@ class MysqlInstaller extends DatabaseInstaller {
'dbname' => false,
'flags' => 0,
'tablePrefix' => $this->getVar( 'wgDBprefix' ) ] );
$status->value = $db;
$status->setDB( $db );
} catch ( DBConnectionError $e ) {
$status->fatal( 'config-connection-error', $e->getMessage() );
}
@ -197,10 +134,7 @@ class MysqlInstaller extends DatabaseInstaller {
return;
}
/**
* @var Database $conn
*/
$conn = $status->value;
$conn = $status->getDB();
$this->selectDatabase( $conn, $this->getVar( 'wgDBname' ) );
# Determine existing default character set
if ( $conn->tableExists( "revision", __METHOD__ ) ) {
@ -260,11 +194,7 @@ class MysqlInstaller extends DatabaseInstaller {
*/
public function getEngines() {
$status = $this->getConnection();
/**
* @var Database $conn
*/
$conn = $status->value;
$conn = $status->getDB();
$engines = [];
$res = $conn->query( 'SHOW ENGINES', __METHOD__ );
@ -297,8 +227,7 @@ class MysqlInstaller extends DatabaseInstaller {
if ( !$status->isOK() ) {
return false;
}
/** @var Database $conn */
$conn = $status->value;
$conn = $status->getDB();
// Get current account name
$currentName = $conn->selectField( '', 'CURRENT_USER()', '', __METHOD__ );
@ -376,83 +305,6 @@ class MysqlInstaller extends DatabaseInstaller {
return "/$r/s";
}
/**
* @return string
*/
public function getSettingsForm() {
if ( $this->canCreateAccounts() ) {
$noCreateMsg = false;
} else {
$noCreateMsg = 'config-db-web-no-create-privs';
}
$s = $this->getWebUserBox( $noCreateMsg );
// Do engine selector
$engines = $this->getEngines();
// If the current default engine is not supported, use an engine that is
if ( !in_array( $this->getVar( '_MysqlEngine' ), $engines ) ) {
$this->setVar( '_MysqlEngine', reset( $engines ) );
}
// If the current default charset is not supported, use a charset that is
$charsets = $this->getCharsets();
if ( !in_array( $this->getVar( '_MysqlCharset' ), $charsets ) ) {
$this->setVar( '_MysqlCharset', reset( $charsets ) );
}
return $s;
}
/**
* @return Status
*/
public function submitSettingsForm() {
$this->setVarsFromRequest( [ '_MysqlEngine', '_MysqlCharset' ] );
$status = $this->submitWebUserBox();
if ( !$status->isOK() ) {
return $status;
}
// 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
try {
MediaWikiServices::getInstance()->getDatabaseFactory()->create( 'mysql', [
'host' => $this->getVar( 'wgDBserver' ),
'user' => $this->getVar( 'wgDBuser' ),
'password' => $this->getVar( 'wgDBpassword' ),
'ssl' => $this->getVar( 'wgDBssl' ),
'dbname' => false,
'flags' => 0,
'tablePrefix' => $this->getVar( 'wgDBprefix' )
] );
} catch ( DBConnectionError $e ) {
return Status::newFatal( 'config-connection-error', $e->getMessage() );
}
}
// Validate engines and charsets
// This is done pre-submit already, so it's just for security
$engines = $this->getEngines();
if ( !in_array( $this->getVar( '_MysqlEngine' ), $engines ) ) {
$this->setVar( '_MysqlEngine', reset( $engines ) );
}
$charsets = $this->getCharsets();
if ( !in_array( $this->getVar( '_MysqlCharset' ), $charsets ) ) {
$this->setVar( '_MysqlCharset', reset( $charsets ) );
}
return Status::newGood();
}
public function preInstall() {
# Add our user callback to installSteps, right before the tables are created.
$callback = [
@ -470,8 +322,7 @@ class MysqlInstaller extends DatabaseInstaller {
if ( !$status->isOK() ) {
return $status;
}
/** @var Database $conn */
$conn = $status->value;
$conn = $status->getDB();
$dbName = $this->getVar( 'wgDBname' );
if ( !$this->databaseExists( $dbName ) ) {
$conn->query(

View file

@ -0,0 +1,100 @@
<?php
namespace MediaWiki\Installer;
use MediaWiki\MediaWikiServices;
use MediaWiki\Status\Status;
use Wikimedia\Rdbms\DBConnectionError;
/**
* @internal
*/
class MysqlSettingsForm extends DatabaseSettingsForm {
/**
* @return string
*/
public function getHtml() {
if ( $this->getMysqlInstaller()->canCreateAccounts() ) {
$noCreateMsg = false;
} else {
$noCreateMsg = 'config-db-web-no-create-privs';
}
$s = $this->getWebUserBox( $noCreateMsg );
// Do engine selector
$engines = $this->getMysqlInstaller()->getEngines();
// If the current default engine is not supported, use an engine that is
if ( !in_array( $this->getVar( '_MysqlEngine' ), $engines ) ) {
$this->setVar( '_MysqlEngine', reset( $engines ) );
}
// If the current default charset is not supported, use a charset that is
$charsets = $this->getMysqlInstaller()->getCharsets();
if ( !in_array( $this->getVar( '_MysqlCharset' ), $charsets ) ) {
$this->setVar( '_MysqlCharset', reset( $charsets ) );
}
return $s;
}
/**
* @return Status
*/
public function submit() {
$this->setVarsFromRequest( [ '_MysqlEngine', '_MysqlCharset' ] );
$status = $this->submitWebUserBox();
if ( !$status->isOK() ) {
return $status;
}
// Validate the create checkbox
$canCreate = $this->getMysqlInstaller()->canCreateAccounts();
if ( !$canCreate ) {
$this->setVar( '_CreateDBAccount', false );
$create = false;
} else {
$create = $this->getVar( '_CreateDBAccount' );
}
if ( !$create ) {
// Test the web account
try {
MediaWikiServices::getInstance()->getDatabaseFactory()->create( 'mysql', [
'host' => $this->getVar( 'wgDBserver' ),
'user' => $this->getVar( 'wgDBuser' ),
'password' => $this->getVar( 'wgDBpassword' ),
'ssl' => $this->getVar( 'wgDBssl' ),
'dbname' => false,
'flags' => 0,
'tablePrefix' => $this->getVar( 'wgDBprefix' )
] );
} catch ( DBConnectionError $e ) {
return Status::newFatal( 'config-connection-error', $e->getMessage() );
}
}
// Validate engines and charsets
// This is done pre-submit already, so it's just for security
$engines = $this->getMysqlInstaller()->getEngines();
if ( !in_array( $this->getVar( '_MysqlEngine' ), $engines ) ) {
$this->setVar( '_MysqlEngine', reset( $engines ) );
}
$charsets = $this->getMysqlInstaller()->getCharsets();
if ( !in_array( $this->getVar( '_MysqlCharset' ), $charsets ) ) {
$this->setVar( '_MysqlCharset', reset( $charsets ) );
}
return Status::newGood();
}
/**
* Downcast the DatabaseInstaller
* @return MysqlInstaller
*/
private function getMysqlInstaller(): MysqlInstaller {
// @phan-suppress-next-line PhanTypeMismatchReturnSuperType
return $this->dbInstaller;
}
}

View file

@ -0,0 +1,104 @@
<?php
namespace MediaWiki\Installer;
use MediaWiki\Html\Html;
use MediaWiki\Status\Status;
use Wikimedia\Rdbms\Database;
/**
* @internal
*/
class PostgresConnectForm extends DatabaseConnectForm {
public function getHtml() {
return $this->getTextBox(
'wgDBserver',
'config-db-host',
[],
$this->webInstaller->getHelpBox( 'config-db-host-help' )
) .
$this->getTextBox( 'wgDBport', 'config-db-port' ) .
$this->getCheckBox( 'wgDBssl', 'config-db-ssl' ) .
"<span class=\"cdx-card\"><span class=\"cdx-card__text\">" .
Html::element(
'span',
[ 'class' => 'cdx-card__text__title' ],
wfMessage( 'config-db-wiki-settings' )->text()
) .
$this->getTextBox(
'wgDBname',
'config-db-name',
[],
$this->webInstaller->getHelpBox( 'config-db-name-help' )
) .
$this->getTextBox(
'wgDBmwschema',
'config-db-schema',
[],
$this->webInstaller->getHelpBox( 'config-db-schema-help' )
) .
"</span></span></span>" .
$this->getInstallUserBox();
}
public function submit() {
// Get variables from the request
$newValues = $this->setVarsFromRequest( [
'wgDBserver',
'wgDBport',
'wgDBssl',
'wgDBname',
'wgDBmwschema'
] );
// Validate them
$status = Status::newGood();
if ( !strlen( $newValues['wgDBname'] ) ) {
$status->fatal( 'config-missing-db-name' );
} elseif ( !preg_match( '/^[a-zA-Z0-9_]+$/', $newValues['wgDBname'] ) ) {
$status->fatal( 'config-invalid-db-name', $newValues['wgDBname'] );
}
if ( !preg_match( '/^[a-zA-Z0-9_]*$/', $newValues['wgDBmwschema'] ) ) {
$status->fatal( 'config-invalid-schema', $newValues['wgDBmwschema'] );
}
// Submit user box
if ( $status->isOK() ) {
$status->merge( $this->submitInstallUserBox() );
}
if ( !$status->isOK() ) {
return $status;
}
$status = $this->getPostgresInstaller()->getPgConnection( 'create-db' );
if ( !$status->isOK() ) {
return $status;
}
/**
* @var Database $conn
*/
$conn = $status->value;
// Check version
$status = PostgresInstaller::meetsMinimumRequirement( $conn );
if ( !$status->isOK() ) {
return $status;
}
$this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
$this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
return Status::newGood();
}
/**
* Downcast the DatabaseInstaller
* @return PostgresInstaller
*/
private function getPostgresInstaller(): PostgresInstaller {
// @phan-suppress-next-line PhanTypeMismatchReturnSuperType
return $this->dbInstaller;
}
}

View file

@ -24,7 +24,6 @@
namespace MediaWiki\Installer;
use InvalidArgumentException;
use MediaWiki\Html\Html;
use MediaWiki\MediaWikiServices;
use MediaWiki\Status\Status;
use Wikimedia\Rdbms\Database;
@ -59,6 +58,9 @@ class PostgresInstaller extends DatabaseInstaller {
protected static $notMinimumVersionMessage = 'config-postgres-old';
public $maxRoleSearchDepth = 5;
/**
* @var DatabasePostgres[]
*/
protected $pgConns = [];
public function getName() {
@ -69,85 +71,12 @@ class PostgresInstaller extends DatabaseInstaller {
return self::checkExtension( 'pgsql' );
}
public function getConnectForm() {
return $this->getTextBox(
'wgDBserver',
'config-db-host',
[],
$this->parent->getHelpBox( 'config-db-host-help' )
) .
$this->getTextBox( 'wgDBport', 'config-db-port' ) .
$this->getCheckBox( 'wgDBssl', 'config-db-ssl' ) .
"<span class=\"cdx-card\"><span class=\"cdx-card__text\">" .
Html::element(
'span',
[ 'class' => 'cdx-card__text__title' ],
wfMessage( 'config-db-wiki-settings' )->text()
) .
$this->getTextBox(
'wgDBname',
'config-db-name',
[],
$this->parent->getHelpBox( 'config-db-name-help' )
) .
$this->getTextBox(
'wgDBmwschema',
'config-db-schema',
[],
$this->parent->getHelpBox( 'config-db-schema-help' )
) .
"</span></span></span>" .
$this->getInstallUserBox();
public function getConnectForm( WebInstaller $webInstaller ): DatabaseConnectForm {
return new PostgresConnectForm( $webInstaller, $this );
}
public function submitConnectForm() {
// Get variables from the request
$newValues = $this->setVarsFromRequest( [
'wgDBserver',
'wgDBport',
'wgDBssl',
'wgDBname',
'wgDBmwschema'
] );
// Validate them
$status = Status::newGood();
if ( !strlen( $newValues['wgDBname'] ) ) {
$status->fatal( 'config-missing-db-name' );
} elseif ( !preg_match( '/^[a-zA-Z0-9_]+$/', $newValues['wgDBname'] ) ) {
$status->fatal( 'config-invalid-db-name', $newValues['wgDBname'] );
}
if ( !preg_match( '/^[a-zA-Z0-9_]*$/', $newValues['wgDBmwschema'] ) ) {
$status->fatal( 'config-invalid-schema', $newValues['wgDBmwschema'] );
}
// Submit user box
if ( $status->isOK() ) {
$status->merge( $this->submitInstallUserBox() );
}
if ( !$status->isOK() ) {
return $status;
}
$status = $this->getPgConnection( 'create-db' );
if ( !$status->isOK() ) {
return $status;
}
/**
* @var Database $conn
*/
$conn = $status->value;
// Check version
$status = static::meetsMinimumRequirement( $conn );
if ( !$status->isOK() ) {
return $status;
}
$this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
$this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
return Status::newGood();
public function getSettingsForm( WebInstaller $webInstaller ): DatabaseSettingsForm {
return new PostgresSettingsForm( $webInstaller, $this );
}
public function getConnection() {
@ -169,10 +98,10 @@ class PostgresInstaller extends DatabaseInstaller {
* @param string $password
* @param string $dbName Database name
* @param string $schema Database schema
* @return Status
* @return ConnectionStatus
*/
protected function openConnectionWithParams( $user, $password, $dbName, $schema ) {
$status = Status::newGood();
$status = new ConnectionStatus;
try {
$db = MediaWikiServices::getInstance()->getDatabaseFactory()->create( 'postgres', [
'host' => $this->getVar( 'wgDBserver' ),
@ -183,7 +112,7 @@ class PostgresInstaller extends DatabaseInstaller {
'dbname' => $dbName,
'schema' => $schema,
] );
$status->value = $db;
$status->setDB( $db );
} catch ( DBConnectionError $e ) {
$status->fatal( 'config-connection-error', $e->getMessage() );
}
@ -194,19 +123,16 @@ class PostgresInstaller extends DatabaseInstaller {
/**
* Get a special type of connection
* @param string $type See openPgConnection() for details.
* @return Status
* @return ConnectionStatus
*/
protected function getPgConnection( $type ) {
public function getPgConnection( $type ) {
if ( isset( $this->pgConns[$type] ) ) {
return Status::newGood( $this->pgConns[$type] );
return new ConnectionStatus( $this->pgConns[$type] );
}
$status = $this->openPgConnection( $type );
if ( $status->isOK() ) {
/**
* @var Database $conn
*/
$conn = $status->value;
$conn = $status->getDB();
$conn->clearFlag( DBO_TRX );
$conn->commit( __METHOD__ );
$this->pgConns[$type] = $conn;
@ -236,7 +162,7 @@ class PostgresInstaller extends DatabaseInstaller {
* - create-schema: A connection to the new DB, for creating schemas and
* other similar objects in the new DB.
* - create-tables: A connection with a role suitable for creating tables.
* @return Status On success, a connection object will be in the value member.
* @return ConnectionStatus On success, a connection object will be in the value member.
*/
protected function openPgConnection( $type ) {
switch ( $type ) {
@ -253,10 +179,7 @@ class PostgresInstaller extends DatabaseInstaller {
case 'create-tables':
$status = $this->openPgConnection( 'create-schema' );
if ( $status->isOK() ) {
/**
* @var Database $conn
*/
$conn = $status->value;
$conn = $status->getDB();
$safeRole = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) );
$conn->query( "SET ROLE $safeRole", __METHOD__ );
}
@ -276,7 +199,7 @@ class PostgresInstaller extends DatabaseInstaller {
array_unshift( $dbs, $this->getVar( 'wgDBname' ) );
}
$conn = false;
$status = Status::newGood();
$status = new ConnectionStatus;
foreach ( $dbs as $db ) {
try {
$p = [
@ -298,7 +221,7 @@ class PostgresInstaller extends DatabaseInstaller {
}
}
if ( $conn !== false ) {
return Status::newGood( $conn );
return new ConnectionStatus( $conn );
} else {
return $status;
}
@ -309,10 +232,7 @@ class PostgresInstaller extends DatabaseInstaller {
if ( !$status->isOK() ) {
return false;
}
/**
* @var Database $conn
*/
$conn = $status->value;
$conn = $status->getDB();
$superuser = $this->getVar( '_InstallUser' );
$row = $conn->selectRow( '"pg_catalog"."pg_roles"', '*',
@ -321,7 +241,7 @@ class PostgresInstaller extends DatabaseInstaller {
return $row;
}
protected function canCreateAccounts() {
public function canCreateAccounts() {
$perms = $this->getInstallUserPermissions();
return $perms && $perms->rolsuper || $perms->rolcreaterole;
}
@ -331,85 +251,12 @@ class PostgresInstaller extends DatabaseInstaller {
return $perms && $perms->rolsuper;
}
public function getSettingsForm() {
if ( $this->canCreateAccounts() ) {
$noCreateMsg = false;
} else {
$noCreateMsg = 'config-db-web-no-create-privs';
}
$s = $this->getWebUserBox( $noCreateMsg );
return $s;
}
public function submitSettingsForm() {
$status = $this->submitWebUserBox();
if ( !$status->isOK() ) {
return $status;
}
$same = $this->getVar( 'wgDBuser' ) === $this->getVar( '_InstallUser' );
if ( $same ) {
$exists = true;
} else {
// Check if the web user exists
// Connect to the database with the install user
$status = $this->getPgConnection( 'create-db' );
if ( !$status->isOK() ) {
return $status;
}
// @phan-suppress-next-line PhanUndeclaredMethod
$exists = $status->value->roleExists( $this->getVar( 'wgDBuser' ) );
}
// Validate the create checkbox
if ( $this->canCreateAccounts() && !$same && !$exists ) {
$create = $this->getVar( '_CreateDBAccount' );
} else {
$this->setVar( '_CreateDBAccount', false );
$create = false;
}
if ( !$create && !$exists ) {
if ( $this->canCreateAccounts() ) {
$msg = 'config-install-user-missing-create';
} else {
$msg = 'config-install-user-missing';
}
return Status::newFatal( $msg, $this->getVar( 'wgDBuser' ) );
}
if ( !$exists ) {
// No more checks to do
return Status::newGood();
}
// Existing web account. Test the connection.
$status = $this->openConnectionToAnyDB(
$this->getVar( 'wgDBuser' ),
$this->getVar( 'wgDBpassword' ) );
if ( !$status->isOK() ) {
return $status;
}
// The web user is conventionally the table owner in PostgreSQL
// installations. Make sure the install user is able to create
// objects on behalf of the web user.
if ( $same || $this->canCreateObjectsForWebUser() ) {
return Status::newGood();
} else {
return Status::newFatal( 'config-pg-not-in-role' );
}
}
/**
* Returns true if the install user is able to create objects owned
* by the web user, false otherwise.
* @return bool
*/
protected function canCreateObjectsForWebUser() {
public function canCreateObjectsForWebUser() {
if ( $this->isSuperUser() ) {
return true;
}
@ -512,9 +359,8 @@ class PostgresInstaller extends DatabaseInstaller {
if ( !$status->isOK() ) {
return $status;
}
/** @var DatabasePostgres $conn */
$conn = $status->value;
'@phan-var DatabasePostgres $conn';
$conn = $status->getDB();
'@phan-var DatabasePostgres $conn'; /** @var DatabasePostgres $conn */
// Create the schema if necessary
$schema = $this->getVar( 'wgDBmwschema' );
@ -550,9 +396,8 @@ class PostgresInstaller extends DatabaseInstaller {
if ( !$status->isOK() ) {
return $status;
}
/** @var DatabasePostgres $conn */
$conn = $status->value;
'@phan-var DatabasePostgres $conn';
$conn = $status->getDB();
'@phan-var DatabasePostgres $conn'; /** @var DatabasePostgres $conn */
$safeuser = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) );
$safepass = $conn->addQuotes( $this->getVar( 'wgDBpassword' ) );
@ -608,10 +453,8 @@ class PostgresInstaller extends DatabaseInstaller {
if ( !$status->isOK() ) {
return $status;
}
/** @var DatabasePostgres $conn */
$conn = $status->value;
'@phan-var DatabasePostgres $conn';
$conn = $status->getDB();
'@phan-var DatabasePostgres $conn'; /** @var DatabasePostgres $conn */
if ( $conn->tableExists( 'archive', __METHOD__ ) ) {
$status->warning( 'config-install-tables-exist' );
@ -671,10 +514,7 @@ class PostgresInstaller extends DatabaseInstaller {
if ( !$status->isOK() ) {
return $status;
}
/**
* @var Database $conn
*/
$conn = $status->value;
$conn = $status->getDB();
$exists = (bool)$conn->selectField( '"pg_catalog"."pg_language"', '1',
[ 'lanname' => 'plpgsql' ], __METHOD__ );

View file

@ -0,0 +1,96 @@
<?php
namespace MediaWiki\Installer;
use MediaWiki\Status\Status;
use Wikimedia\Rdbms\DatabasePostgres;
/**
* @internal
*/
class PostgresSettingsForm extends DatabaseSettingsForm {
public function getHtml() {
if ( $this->getPostgresInstaller()->canCreateAccounts() ) {
$noCreateMsg = false;
} else {
$noCreateMsg = 'config-db-web-no-create-privs';
}
$s = $this->getWebUserBox( $noCreateMsg );
return $s;
}
public function submit() {
$status = $this->submitWebUserBox();
if ( !$status->isOK() ) {
return $status;
}
$same = $this->getVar( 'wgDBuser' ) === $this->getVar( '_InstallUser' );
if ( $same ) {
$exists = true;
} else {
// Check if the web user exists
// Connect to the database with the install user
$status = $this->getPostgresInstaller()->getPgConnection( 'create-db' );
if ( !$status->isOK() ) {
return $status;
}
$conn = $status->getDB();
'@phan-var DatabasePostgres $conn'; /** @var DatabasePostgres $conn */
$exists = $conn->roleExists( $this->getVar( 'wgDBuser' ) );
}
// Validate the create checkbox
if ( $this->getPostgresInstaller()->canCreateAccounts() && !$same && !$exists ) {
$create = $this->getVar( '_CreateDBAccount' );
} else {
$this->setVar( '_CreateDBAccount', false );
$create = false;
}
if ( !$create && !$exists ) {
if ( $this->getPostgresInstaller()->canCreateAccounts() ) {
$msg = 'config-install-user-missing-create';
} else {
$msg = 'config-install-user-missing';
}
return Status::newFatal( $msg, $this->getVar( 'wgDBuser' ) );
}
if ( !$exists ) {
// No more checks to do
return Status::newGood();
}
// Existing web account. Test the connection.
$status = $this->getPostgresInstaller()->openConnectionToAnyDB(
$this->getVar( 'wgDBuser' ),
$this->getVar( 'wgDBpassword' ) );
if ( !$status->isOK() ) {
return $status;
}
// The web user is conventionally the table owner in PostgreSQL
// installations. Make sure the install user is able to create
// objects on behalf of the web user.
if ( $same || $this->getPostgresInstaller()->canCreateObjectsForWebUser() ) {
return Status::newGood();
} else {
return Status::newFatal( 'config-pg-not-in-role' );
}
}
/**
* Downcast the DatabaseInstaller
* @return PostgresInstaller
*/
private function getPostgresInstaller(): PostgresInstaller {
// @phan-suppress-next-line PhanTypeMismatchReturnSuperType
return $this->dbInstaller;
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace MediaWiki\Installer;
use MediaWiki\Status\Status;
/**
* @internal
*/
class SqliteConnectForm extends DatabaseConnectForm {
public function getHtml() {
return $this->getTextBox(
'wgSQLiteDataDir',
'config-sqlite-dir', [],
$this->webInstaller->getHelpBox( 'config-sqlite-dir-help' )
) .
$this->getTextBox(
'wgDBname',
'config-db-name',
[],
$this->webInstaller->getHelpBox( 'config-sqlite-name-help' )
);
}
/**
* @return Status
*/
public function submit() {
$this->setVarsFromRequest( [ 'wgSQLiteDataDir', 'wgDBname' ] );
# Try realpath() if the directory already exists
$dir = SqliteInstaller::realpath( $this->getVar( 'wgSQLiteDataDir' ) );
$result = SqliteInstaller::checkDataDir( $dir );
if ( $result->isOK() ) {
# Try expanding again in case we've just created it
$dir = SqliteInstaller::realpath( $dir );
$this->setVar( 'wgSQLiteDataDir', $dir );
}
# Table prefix is not used on SQLite, keep it empty
$this->setVar( 'wgDBprefix', '' );
return $result;
}
}

View file

@ -60,6 +60,14 @@ class SqliteInstaller extends DatabaseInstaller {
return self::checkExtension( 'pdo_sqlite' );
}
public function getConnectForm( WebInstaller $webInstaller ): DatabaseConnectForm {
return new SqliteConnectForm( $webInstaller, $this );
}
public function getSettingsForm( WebInstaller $webInstaller ): DatabaseSettingsForm {
return new DatabaseSettingsForm( $webInstaller, $this );
}
/**
* @return Status
*/
@ -92,20 +100,6 @@ class SqliteInstaller extends DatabaseInstaller {
return $defaults;
}
public function getConnectForm() {
return $this->getTextBox(
'wgSQLiteDataDir',
'config-sqlite-dir', [],
$this->parent->getHelpBox( 'config-sqlite-dir-help' )
) .
$this->getTextBox(
'wgDBname',
'config-db-name',
[],
$this->parent->getHelpBox( 'config-sqlite-name-help' )
);
}
/**
* Safe wrapper for PHP's realpath() that fails gracefully if it's unable to canonicalize the path.
*
@ -113,36 +107,16 @@ class SqliteInstaller extends DatabaseInstaller {
*
* @return string
*/
private static function realpath( $path ) {
public static function realpath( $path ) {
return realpath( $path ) ?: $path;
}
/**
* @return Status
*/
public function submitConnectForm() {
$this->setVarsFromRequest( [ 'wgSQLiteDataDir', 'wgDBname' ] );
# Try realpath() if the directory already exists
$dir = self::realpath( $this->getVar( 'wgSQLiteDataDir' ) );
$result = self::checkDataDir( $dir );
if ( $result->isOK() ) {
# Try expanding again in case we've just created it
$dir = self::realpath( $dir );
$this->setVar( 'wgSQLiteDataDir', $dir );
}
# Table prefix is not used on SQLite, keep it empty
$this->setVar( 'wgDBprefix', '' );
return $result;
}
/**
* Check if the data directory is writable or can be created
* @param string $dir Path to the data directory
* @return Status Return fatal Status if $dir un-writable or no permission to create a directory
*/
private static function checkDataDir( $dir ): Status {
public static function checkDataDir( $dir ): Status {
if ( is_dir( $dir ) ) {
if ( !is_readable( $dir ) ) {
return Status::newFatal( 'config-sqlite-dir-unwritable', $dir );
@ -185,17 +159,17 @@ class SqliteInstaller extends DatabaseInstaller {
}
/**
* @return Status
* @return ConnectionStatus
*/
public function openConnection() {
$status = Status::newGood();
$status = new ConnectionStatus;
$dir = $this->getVar( 'wgSQLiteDataDir' );
$dbName = $this->getVar( 'wgDBname' );
try {
$db = MediaWikiServices::getInstance()->getDatabaseFactory()->create(
'sqlite', [ 'dbname' => $dbName, 'dbDirectory' => $dir ]
);
$status->value = $db;
$status->setDB( $db );
} catch ( DBConnectionError $e ) {
$status->fatal( 'config-sqlite-connection-error', $e->getMessage() );
}
@ -314,7 +288,7 @@ EOT;
// when the DB is being read and written concurrently.
// This causes the DB to be created in this mode
// so we only have to do this on creation.
$mainConnStatus->value->query( "PRAGMA journal_mode=WAL", __METHOD__ );
$mainConnStatus->getDB()->query( "PRAGMA journal_mode=WAL", __METHOD__ );
return $mainConnStatus;
}

View file

@ -98,7 +98,7 @@ class WebInstallerDBConnect extends WebInstallerPage {
]
) .
Html::element( 'h3', [], wfMessage( 'config-header-' . $type )->text() ) .
$installer->getConnectForm() .
$installer->getConnectForm( $this->parent )->getHtml() .
"</div>\n";
}
@ -126,7 +126,7 @@ class WebInstallerDBConnect extends WebInstallerPage {
return Status::newFatal( 'config-invalid-db-type' );
}
return $installer->submitConnectForm();
return $installer->getConnectForm( $this->parent )->submit();
}
}

View file

@ -28,10 +28,11 @@ class WebInstallerDBSettings extends WebInstallerPage {
*/
public function execute() {
$installer = $this->parent->getDBInstaller( $this->getVar( 'wgDBtype' ) );
$form = $installer->getSettingsForm( $this->parent );
$r = $this->parent->request;
if ( $r->wasPosted() ) {
$status = $installer->submitSettingsForm();
$status = $form->submit();
if ( $status === false ) {
return 'skip';
} elseif ( $status->isGood() ) {
@ -41,13 +42,13 @@ class WebInstallerDBSettings extends WebInstallerPage {
}
}
$form = $installer->getSettingsForm();
if ( $form === false ) {
$formHtml = $form->getHtml();
if ( $formHtml === false ) {
return 'skip';
}
$this->startForm();
$this->addHTML( $form );
$this->addHTML( $formHtml );
$this->endForm();
return null;