MediaWiki  master
DatabaseInstaller.php
Go to the documentation of this file.
1 <?php
30 
37 abstract class DatabaseInstaller {
38 
44  public $parent;
45 
49  public static $minimumVersion;
50 
54  protected static $notMinimumVersionMessage;
55 
61  public $db = null;
62 
68  protected $internalDefaults = [];
69 
75  protected $globalNames = [];
76 
84  public static function meetsMinimumRequirement( $serverVersion ) {
85  if ( version_compare( $serverVersion, static::$minimumVersion ) < 0 ) {
86  return Status::newFatal(
87  static::$notMinimumVersionMessage, static::$minimumVersion, $serverVersion
88  );
89  }
90 
91  return Status::newGood();
92  }
93 
97  abstract public function getName();
98 
102  abstract public function isCompiled();
103 
109  public function checkPrerequisites() {
110  return Status::newGood();
111  }
112 
120  abstract public function getConnectForm();
121 
131  abstract public function submitConnectForm();
132 
140  public function getSettingsForm() {
141  return false;
142  }
143 
150  public function submitSettingsForm() {
151  return Status::newGood();
152  }
153 
162  abstract public function openConnection();
163 
170  abstract public function setupDatabase();
171 
182  public function getConnection() {
183  if ( $this->db ) {
184  return Status::newGood( $this->db );
185  }
186 
187  $status = $this->openConnection();
188  if ( $status->isOK() ) {
189  $this->db = $status->value;
190  // Enable autocommit
191  $this->db->clearFlag( DBO_TRX );
192  $this->db->commit( __METHOD__ );
193  }
194 
195  return $status;
196  }
197 
206  private function stepApplySourceFile(
207  $sourceFileMethod,
208  $stepName,
209  $archiveTableMustNotExist = false
210  ) {
211  $status = $this->getConnection();
212  if ( !$status->isOK() ) {
213  return $status;
214  }
215  $this->db->selectDB( $this->getVar( 'wgDBname' ) );
216 
217  if ( $archiveTableMustNotExist && $this->db->tableExists( 'archive', __METHOD__ ) ) {
218  $status->warning( "config-$stepName-tables-exist" );
219  $this->enableLB();
220 
221  return $status;
222  }
223 
224  $this->db->setFlag( DBO_DDLMODE ); // For Oracle's handling of schema files
225  $this->db->begin( __METHOD__ );
226 
227  $error = $this->db->sourceFile(
228  call_user_func( [ $this, $sourceFileMethod ], $this->db )
229  );
230  if ( $error !== true ) {
231  $this->db->reportQueryError( $error, 0, '', __METHOD__ );
232  $this->db->rollback( __METHOD__ );
233  $status->fatal( "config-$stepName-tables-failed", $error );
234  } else {
235  $this->db->commit( __METHOD__ );
236  }
237  // Resume normal operations
238  if ( $status->isOK() ) {
239  $this->enableLB();
240  }
241 
242  return $status;
243  }
244 
250  public function createTables() {
251  return $this->stepApplySourceFile( 'getSchemaPath', 'install', true );
252  }
253 
259  public function insertUpdateKeys() {
260  return $this->stepApplySourceFile( 'getUpdateKeysPath', 'updates', false );
261  }
262 
271  private function getSqlFilePath( $db, $filename ) {
272  global $IP;
273 
274  $dbmsSpecificFilePath = "$IP/maintenance/" . $db->getType() . "/$filename";
275  if ( file_exists( $dbmsSpecificFilePath ) ) {
276  return $dbmsSpecificFilePath;
277  } else {
278  return "$IP/maintenance/$filename";
279  }
280  }
281 
289  public function getSchemaPath( $db ) {
290  return $this->getSqlFilePath( $db, 'tables.sql' );
291  }
292 
300  public function getUpdateKeysPath( $db ) {
301  return $this->getSqlFilePath( $db, 'update-keys.sql' );
302  }
303 
308  public function createExtensionTables() {
309  $status = $this->getConnection();
310  if ( !$status->isOK() ) {
311  return $status;
312  }
313 
314  // Now run updates to create tables for old extensions
315  DatabaseUpdater::newForDB( $this->db )->doUpdates( [ 'extensions' ] );
316 
317  return $status;
318  }
319 
325  abstract public function getLocalSettings();
326 
332  public function getSchemaVars() {
333  return [];
334  }
335 
342  public function setupSchemaVars() {
343  $status = $this->getConnection();
344  if ( $status->isOK() ) {
345  // @phan-suppress-next-line PhanUndeclaredMethod
346  $status->value->setSchemaVars( $this->getSchemaVars() );
347  } else {
348  $msg = __METHOD__ . ': unexpected error while establishing'
349  . ' a database connection with message: '
350  . $status->getMessage()->plain();
351  throw new MWException( $msg );
352  }
353  }
354 
360  public function enableLB() {
361  $status = $this->getConnection();
362  if ( !$status->isOK() ) {
363  throw new MWException( __METHOD__ . ': unexpected DB connection error' );
364  }
365 
366  MediaWikiServices::resetGlobalInstance();
367  $services = MediaWikiServices::getInstance();
368 
369  $connection = $status->value;
370  $services->redefineService( 'DBLoadBalancerFactory', function () use ( $connection ) {
371  return LBFactorySingle::newFromConnection( $connection );
372  } );
373  }
374 
381  public function doUpgrade() {
382  $this->setupSchemaVars();
383  $this->enableLB();
384 
385  $ret = true;
386  ob_start( [ $this, 'outputHandler' ] );
387  $up = DatabaseUpdater::newForDB( $this->db );
388  try {
389  $up->doUpdates();
390  $up->purgeCache();
391  } catch ( MWException $e ) {
392  echo "\nAn error occurred:\n";
393  echo $e->getText();
394  $ret = false;
395  } catch ( Exception $e ) {
396  echo "\nAn error occurred:\n";
397  echo $e->getMessage();
398  $ret = false;
399  }
400  ob_end_flush();
401 
402  return $ret;
403  }
404 
410  public function preInstall() {
411  }
412 
416  public function preUpgrade() {
417  }
418 
423  public function getGlobalNames() {
424  return $this->globalNames;
425  }
426 
432  public function __construct( $parent ) {
433  $this->parent = $parent;
434  }
435 
443  protected static function checkExtension( $name ) {
444  return extension_loaded( $name );
445  }
446 
451  public function getReadableName() {
452  // Messages: config-type-mysql, config-type-postgres, config-type-sqlite
453  return wfMessage( 'config-type-' . $this->getName() )->text();
454  }
455 
460  public function getGlobalDefaults() {
461  $defaults = [];
462  foreach ( $this->getGlobalNames() as $var ) {
463  if ( isset( $GLOBALS[$var] ) ) {
464  $defaults[$var] = $GLOBALS[$var];
465  }
466  }
467  return $defaults;
468  }
469 
474  public function getInternalDefaults() {
476  }
477 
484  public function getVar( $var, $default = null ) {
485  $defaults = $this->getGlobalDefaults();
486  $internal = $this->getInternalDefaults();
487  if ( isset( $defaults[$var] ) ) {
488  $default = $defaults[$var];
489  } elseif ( isset( $internal[$var] ) ) {
490  $default = $internal[$var];
491  }
492 
493  return $this->parent->getVar( $var, $default );
494  }
495 
501  public function setVar( $name, $value ) {
502  $this->parent->setVar( $name, $value );
503  }
504 
514  public function getTextBox( $var, $label, $attribs = [], $helpData = "" ) {
515  $name = $this->getName() . '_' . $var;
516  $value = $this->getVar( $var );
517  if ( !isset( $attribs ) ) {
518  $attribs = [];
519  }
520 
521  return $this->parent->getTextBox( [
522  'var' => $var,
523  'label' => $label,
524  'attribs' => $attribs,
525  'controlName' => $name,
526  'value' => $value,
527  'help' => $helpData
528  ] );
529  }
530 
541  public function getPasswordBox( $var, $label, $attribs = [], $helpData = "" ) {
542  $name = $this->getName() . '_' . $var;
543  $value = $this->getVar( $var );
544  if ( !isset( $attribs ) ) {
545  $attribs = [];
546  }
547 
548  return $this->parent->getPasswordBox( [
549  'var' => $var,
550  'label' => $label,
551  'attribs' => $attribs,
552  'controlName' => $name,
553  'value' => $value,
554  'help' => $helpData
555  ] );
556  }
557 
567  public function getCheckBox( $var, $label, $attribs = [], $helpData = "" ) {
568  $name = $this->getName() . '_' . $var;
569  $value = $this->getVar( $var );
570 
571  return $this->parent->getCheckBox( [
572  'var' => $var,
573  'label' => $label,
574  'attribs' => $attribs,
575  'controlName' => $name,
576  'value' => $value,
577  'help' => $helpData
578  ] );
579  }
580 
593  public function getRadioSet( $params ) {
594  $params['controlName'] = $this->getName() . '_' . $params['var'];
595  $params['value'] = $this->getVar( $params['var'] );
596 
597  return $this->parent->getRadioSet( $params );
598  }
599 
607  public function setVarsFromRequest( $varNames ) {
608  return $this->parent->setVarsFromRequest( $varNames, $this->getName() . '_' );
609  }
610 
621  public function needsUpgrade() {
622  $status = $this->getConnection();
623  if ( !$status->isOK() ) {
624  return false;
625  }
626 
627  try {
628  $this->db->selectDB( $this->getVar( 'wgDBname' ) );
629  } catch ( DBConnectionError $e ) {
630  // Don't catch DBConnectionError
631  throw $e;
632  } catch ( DBExpectedError $e ) {
633  return false;
634  }
635 
636  return $this->db->tableExists( 'cur', __METHOD__ ) ||
637  $this->db->tableExists( 'revision', __METHOD__ );
638  }
639 
645  public function getInstallUserBox() {
646  return Html::openElement( 'fieldset' ) .
647  Html::element( 'legend', [], wfMessage( 'config-db-install-account' )->text() ) .
648  $this->getTextBox(
649  '_InstallUser',
650  'config-db-username',
651  [ 'dir' => 'ltr' ],
652  $this->parent->getHelpBox( 'config-db-install-username' )
653  ) .
654  $this->getPasswordBox(
655  '_InstallPassword',
656  'config-db-password',
657  [ 'dir' => 'ltr' ],
658  $this->parent->getHelpBox( 'config-db-install-password' )
659  ) .
660  Html::closeElement( 'fieldset' );
661  }
662 
667  public function submitInstallUserBox() {
668  $this->setVarsFromRequest( [ '_InstallUser', '_InstallPassword' ] );
669 
670  return Status::newGood();
671  }
672 
680  public function getWebUserBox( $noCreateMsg = false ) {
681  $wrapperStyle = $this->getVar( '_SameAccount' ) ? 'display: none' : '';
682  $s = Html::openElement( 'fieldset' ) .
683  Html::element( 'legend', [], wfMessage( 'config-db-web-account' )->text() ) .
684  $this->getCheckBox(
685  '_SameAccount', 'config-db-web-account-same',
686  [ 'class' => 'hideShowRadio', 'rel' => 'dbOtherAccount' ]
687  ) .
688  Html::openElement( 'div', [ 'id' => 'dbOtherAccount', 'style' => $wrapperStyle ] ) .
689  $this->getTextBox( 'wgDBuser', 'config-db-username' ) .
690  $this->getPasswordBox( 'wgDBpassword', 'config-db-password' ) .
691  $this->parent->getHelpBox( 'config-db-web-help' );
692  if ( $noCreateMsg ) {
693  $s .= Html::warningBox( wfMessage( $noCreateMsg )->plain(), 'config-warning-box' );
694  } else {
695  $s .= $this->getCheckBox( '_CreateDBAccount', 'config-db-web-create' );
696  }
697  $s .= Html::closeElement( 'div' ) . Html::closeElement( 'fieldset' );
698 
699  return $s;
700  }
701 
707  public function submitWebUserBox() {
708  $this->setVarsFromRequest(
709  [ 'wgDBuser', 'wgDBpassword', '_SameAccount', '_CreateDBAccount' ]
710  );
711 
712  if ( $this->getVar( '_SameAccount' ) ) {
713  $this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
714  $this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
715  }
716 
717  if ( $this->getVar( '_CreateDBAccount' ) && strval( $this->getVar( 'wgDBpassword' ) ) == '' ) {
718  return Status::newFatal( 'config-db-password-empty', $this->getVar( 'wgDBuser' ) );
719  }
720 
721  return Status::newGood();
722  }
723 
729  public function populateInterwikiTable() {
730  $status = $this->getConnection();
731  if ( !$status->isOK() ) {
732  return $status;
733  }
734  $this->db->selectDB( $this->getVar( 'wgDBname' ) );
735 
736  if ( $this->db->selectRow( 'interwiki', '1', [], __METHOD__ ) ) {
737  $status->warning( 'config-install-interwiki-exists' );
738 
739  return $status;
740  }
741  global $IP;
742  Wikimedia\suppressWarnings();
743  $rows = file( "$IP/maintenance/interwiki.list",
744  FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES );
745  Wikimedia\restoreWarnings();
746  $interwikis = [];
747  if ( !$rows ) {
748  return Status::newFatal( 'config-install-interwiki-list' );
749  }
750  foreach ( $rows as $row ) {
751  $row = preg_replace( '/^\s*([^#]*?)\s*(#.*)?$/', '\\1', $row ); // strip comments - whee
752  if ( $row == "" ) {
753  continue;
754  }
755  $row .= "|";
756  $interwikis[] = array_combine(
757  [ 'iw_prefix', 'iw_url', 'iw_local', 'iw_api', 'iw_wikiid' ],
758  explode( '|', $row )
759  );
760  }
761  $this->db->insert( 'interwiki', $interwikis, __METHOD__ );
762 
763  return Status::newGood();
764  }
765 
766  public function outputHandler( $string ) {
767  return htmlspecialchars( $string );
768  }
769 }
DatabaseInstaller\$globalNames
array $globalNames
Array of MW configuration globals this class uses.
Definition: DatabaseInstaller.php:75
Wikimedia\Rdbms\Database
Relational database abstraction object.
Definition: Database.php:49
DatabaseUpdater\newForDB
static newForDB(IMaintainableDatabase $db, $shared=false, Maintenance $maintenance=null)
Definition: DatabaseUpdater.php:191
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:69
DatabaseInstaller\checkExtension
static checkExtension( $name)
Convenience function.
Definition: DatabaseInstaller.php:443
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:130
DatabaseInstaller\doUpgrade
doUpgrade()
Perform database upgrades.
Definition: DatabaseInstaller.php:381
WebInstaller
Class for the core installer web interface.
Definition: WebInstaller.php:32
DatabaseInstaller\getConnection
getConnection()
Connect to the database using the administrative user/password currently defined in the session.
Definition: DatabaseInstaller.php:182
DatabaseInstaller\getTextBox
getTextBox( $var, $label, $attribs=[], $helpData="")
Get a labelled text box to configure a local variable.
Definition: DatabaseInstaller.php:514
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1263
DatabaseInstaller\checkPrerequisites
checkPrerequisites()
Checks for installation prerequisites other than those checked by isCompiled()
Definition: DatabaseInstaller.php:109
DatabaseInstaller\getSchemaVars
getSchemaVars()
Override this to provide DBMS-specific schema variables, to be substituted into tables....
Definition: DatabaseInstaller.php:332
$s
$s
Definition: mergeMessageFileList.php:185
DatabaseInstaller\preUpgrade
preUpgrade()
Allow DB installers a chance to make checks before upgrade.
Definition: DatabaseInstaller.php:416
DatabaseInstaller\preInstall
preInstall()
Allow DB installers a chance to make last-minute changes before installation occurs.
Definition: DatabaseInstaller.php:410
DatabaseInstaller\submitSettingsForm
submitSettingsForm()
Set variables based on the request array, assuming it was submitted via the form return by getSetting...
Definition: DatabaseInstaller.php:150
DatabaseInstaller\getLocalSettings
getLocalSettings()
Get the DBMS-specific options for LocalSettings.php generation.
DBO_TRX
const DBO_TRX
Definition: defines.php:12
DatabaseInstaller\getReadableName
getReadableName()
Get the internationalised name for this DBMS.
Definition: DatabaseInstaller.php:451
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
DatabaseInstaller\createTables
createTables()
Create database tables from scratch.
Definition: DatabaseInstaller.php:250
DatabaseInstaller\getInternalDefaults
getInternalDefaults()
Get a name=>value map of internal variables used during installation.
Definition: DatabaseInstaller.php:474
DatabaseInstaller\getSqlFilePath
getSqlFilePath( $db, $filename)
Return a path to the DBMS-specific SQL file if it exists, otherwise default SQL file.
Definition: DatabaseInstaller.php:271
DatabaseInstaller\getConnectForm
getConnectForm()
Get HTML for a web form that configures this database.
MWException
MediaWiki exception.
Definition: MWException.php:26
DatabaseInstaller\getPasswordBox
getPasswordBox( $var, $label, $attribs=[], $helpData="")
Get a labelled password box to configure a local variable.
Definition: DatabaseInstaller.php:541
Wikimedia\Rdbms\LBFactorySingle
An LBFactory class that always returns a single database object.
Definition: LBFactorySingle.php:32
MWException\getText
getText()
Get the text to display when reporting the error on the command line.
Definition: MWException.php:139
DatabaseInstaller\submitConnectForm
submitConnectForm()
Set variables based on the request array, assuming it was submitted via the form returned by getConne...
DatabaseInstaller\getUpdateKeysPath
getUpdateKeysPath( $db)
Return a path to the DBMS-specific update key file, otherwise default to update-keys....
Definition: DatabaseInstaller.php:300
DatabaseInstaller\submitWebUserBox
submitWebUserBox()
Submit the form from getWebUserBox().
Definition: DatabaseInstaller.php:707
DatabaseInstaller\$minimumVersion
static string $minimumVersion
Set by subclasses.
Definition: DatabaseInstaller.php:49
DatabaseInstaller\getName
getName()
Return the internal name, e.g.
DatabaseInstaller\outputHandler
outputHandler( $string)
Definition: DatabaseInstaller.php:766
DatabaseInstaller\setupSchemaVars
setupSchemaVars()
Set appropriate schema variables in the current database connection.
Definition: DatabaseInstaller.php:342
DatabaseInstaller\getSchemaPath
getSchemaPath( $db)
Return a path to the DBMS-specific schema file, otherwise default to tables.sql.
Definition: DatabaseInstaller.php:289
DatabaseInstaller\getWebUserBox
getWebUserBox( $noCreateMsg=false)
Get a standard web-user fieldset.
Definition: DatabaseInstaller.php:680
DatabaseInstaller\getRadioSet
getRadioSet( $params)
Get a set of labelled radio buttons.
Definition: DatabaseInstaller.php:593
DatabaseInstaller\stepApplySourceFile
stepApplySourceFile( $sourceFileMethod, $stepName, $archiveTableMustNotExist=false)
Apply a SQL source file to the database as part of running an installation step.
Definition: DatabaseInstaller.php:206
DatabaseInstaller\getGlobalDefaults
getGlobalDefaults()
Get a name=>value map of MW configuration globals for the default values.
Definition: DatabaseInstaller.php:460
DatabaseInstaller\getVar
getVar( $var, $default=null)
Get a variable, taking local defaults into account.
Definition: DatabaseInstaller.php:484
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
DBO_DDLMODE
const DBO_DDLMODE
Definition: defines.php:16
DatabaseInstaller\createExtensionTables
createExtensionTables()
Create the tables for each extension the user enabled.
Definition: DatabaseInstaller.php:308
DatabaseInstaller\submitInstallUserBox
submitInstallUserBox()
Submit a standard install user fieldset.
Definition: DatabaseInstaller.php:667
DatabaseInstaller
Base class for DBMS-specific installation helper classes.
Definition: DatabaseInstaller.php:37
DatabaseInstaller\getGlobalNames
getGlobalNames()
Get an array of MW configuration globals that will be configured by this class.
Definition: DatabaseInstaller.php:423
DatabaseInstaller\getSettingsForm
getSettingsForm()
Get HTML for a web form that retrieves settings used for installation.
Definition: DatabaseInstaller.php:140
Wikimedia\Rdbms\DBExpectedError
Base class for the more common types of database errors.
Definition: DBExpectedError.php:32
DatabaseInstaller\openConnection
openConnection()
Open a connection to the database using the administrative user/password currently defined in the ses...
DatabaseInstaller\$notMinimumVersionMessage
static string $notMinimumVersionMessage
Set by subclasses.
Definition: DatabaseInstaller.php:54
DatabaseInstaller\enableLB
enableLB()
Set up LBFactory so that wfGetDB() etc.
Definition: DatabaseInstaller.php:360
DatabaseInstaller\populateInterwikiTable
populateInterwikiTable()
Common function for databases that don't understand the MySQLish syntax of interwiki....
Definition: DatabaseInstaller.php:729
DatabaseInstaller\setVar
setVar( $name, $value)
Convenience alias for $this->parent->setVar()
Definition: DatabaseInstaller.php:501
DatabaseInstaller\getInstallUserBox
getInstallUserBox()
Get a standard install-user fieldset.
Definition: DatabaseInstaller.php:645
DatabaseInstaller\setVarsFromRequest
setVarsFromRequest( $varNames)
Convenience function to set variables based on form data.
Definition: DatabaseInstaller.php:607
Wikimedia\Rdbms\DBConnectionError
Definition: DBConnectionError.php:26
Wikimedia\Rdbms\IDatabase\getType
getType()
Get the type of the DBMS (e.g.
DatabaseInstaller\meetsMinimumRequirement
static meetsMinimumRequirement( $serverVersion)
Whether the provided version meets the necessary requirements for this type.
Definition: DatabaseInstaller.php:84
DatabaseInstaller\$parent
WebInstaller $parent
The Installer object.
Definition: DatabaseInstaller.php:44
DatabaseInstaller\isCompiled
isCompiled()
DatabaseInstaller\$db
Database $db
The database connection.
Definition: DatabaseInstaller.php:61
DatabaseInstaller\setupDatabase
setupDatabase()
Create the database and return a Status object indicating success or failure.
DatabaseInstaller\insertUpdateKeys
insertUpdateKeys()
Insert update keys into table to prevent running unneded updates.
Definition: DatabaseInstaller.php:259
$IP
$IP
Definition: WebStart.php:41
DatabaseInstaller\$internalDefaults
array $internalDefaults
Internal variables for installation.
Definition: DatabaseInstaller.php:68
$GLOBALS
$GLOBALS['IP']
Definition: ComposerHookHandler.php:6
DatabaseInstaller\getCheckBox
getCheckBox( $var, $label, $attribs=[], $helpData="")
Get a labelled checkbox to configure a local boolean variable.
Definition: DatabaseInstaller.php:567
DatabaseInstaller\__construct
__construct( $parent)
Construct and initialise parent.
Definition: DatabaseInstaller.php:432
DatabaseInstaller\needsUpgrade
needsUpgrade()
Determine whether an existing installation of MediaWiki is present in the configured administrative c...
Definition: DatabaseInstaller.php:621