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 }
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:69
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:231
$IP
Definition: WebStart.php:41
__construct( $parent)
Construct and initialise parent.
preUpgrade()
Allow DB installers a chance to make checks before upgrade.
preInstall()
Allow DB installers a chance to make last-minute changes before installation occurs.
needsUpgrade()
Determine whether an existing installation of MediaWiki is present in the configured administrative c...
submitSettingsForm()
Set variables based on the request array, assuming it was submitted via the form return by getSetting...
getLocalSettings()
Get the DBMS-specific options for LocalSettings.php generation.
array $globalNames
Array of MW configuration globals this class uses.
static openElement( $element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing &#39;/&#39;...
Definition: Html.php:251
getSqlFilePath( $db, $filename)
Return a path to the DBMS-specific SQL file if it exists, otherwise default SQL file.
doUpgrade()
Perform database upgrades.
createTables()
Create database tables from scratch.
getTextBox( $var, $label, $attribs=[], $helpData="")
Get a labelled text box to configure a local variable.
const DBO_DDLMODE
Definition: defines.php:16
getConnection()
Connect to the database using the administrative user/password currently defined in the session...
getSchemaVars()
Override this to provide DBMS-specific schema variables, to be substituted into tables.sql and other schema files.
checkPrerequisites()
Checks for installation prerequisites other than those checked by isCompiled()
submitWebUserBox()
Submit the form from getWebUserBox().
getName()
Return the internal name, e.g.
setupSchemaVars()
Set appropriate schema variables in the current database connection.
getConnectForm()
Get HTML for a web form that configures this database.
getSchemaPath( $db)
Return a path to the DBMS-specific schema file, otherwise default to tables.sql.
getPasswordBox( $var, $label, $attribs=[], $helpData="")
Get a labelled password box to configure a local variable.
getReadableName()
Get the internationalised name for this DBMS.
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
createExtensionTables()
Create the tables for each extension the user enabled.
getInternalDefaults()
Get a name=>value map of internal variables used during installation.
static closeElement( $element)
Returns "</$element>".
Definition: Html.php:315
getGlobalDefaults()
Get a name=>value map of MW configuration globals for the default values.
getWebUserBox( $noCreateMsg=false)
Get a standard web-user fieldset.
$GLOBALS['IP']
submitConnectForm()
Set variables based on the request array, assuming it was submitted via the form returned by getConne...
static string $minimumVersion
Set by subclasses.
getUpdateKeysPath( $db)
Return a path to the DBMS-specific update key file, otherwise default to update-keys.sql.
submitInstallUserBox()
Submit a standard install user fieldset.
static warningBox( $html, $className='')
Return a warning box.
Definition: Html.php:726
const DBO_TRX
Definition: defines.php:12
getInstallUserBox()
Get a standard install-user fieldset.
getVar( $var, $default=null)
Get a variable, taking local defaults into account.
getText()
Get the text to display when reporting the error on the command line.
static meetsMinimumRequirement( $serverVersion)
Whether the provided version meets the necessary requirements for this type.
getType()
Get the type of the DBMS (e.g.
Database $db
The database connection.
getRadioSet( $params)
Get a set of labelled radio buttons.
setupDatabase()
Create the database and return a Status object indicating success or failure.
stepApplySourceFile( $sourceFileMethod, $stepName, $archiveTableMustNotExist=false)
Apply a SQL source file to the database as part of running an installation step.
setVar( $name, $value)
Convenience alias for $this->parent->setVar()
Base class for DBMS-specific installation helper classes.
static newForDB(IMaintainableDatabase $db, $shared=false, Maintenance $maintenance=null)
openConnection()
Open a connection to the database using the administrative user/password currently defined in the ses...
insertUpdateKeys()
Insert update keys into table to prevent running unneded updates.
populateInterwikiTable()
Common function for databases that don&#39;t understand the MySQLish syntax of interwiki.sql.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
setVarsFromRequest( $varNames)
Convenience function to set variables based on form data.
WebInstaller $parent
The Installer object.
getGlobalNames()
Get an array of MW configuration globals that will be configured by this class.
getCheckBox( $var, $label, $attribs=[], $helpData="")
Get a labelled checkbox to configure a local boolean variable.
getSettingsForm()
Get HTML for a web form that retrieves settings used for installation.
static checkExtension( $name)
Convenience function.
static string $notMinimumVersionMessage
Set by subclasses.
enableLB()
Set up LBFactory so that wfGetDB() etc.
Base class for the more common types of database errors.
array $internalDefaults
Internal variables for installation.