MediaWiki  master
DatabaseInstaller.php
Go to the documentation of this file.
1 <?php
24 use Wikimedia\AtEase\AtEase;
30 
38 abstract class DatabaseInstaller {
39 
45  public $parent;
46 
50  public static $minimumVersion;
51 
55  protected static $notMinimumVersionMessage;
56 
62  public $db = null;
63 
69  protected $internalDefaults = [];
70 
76  protected $globalNames = [];
77 
85  public static function meetsMinimumRequirement( $serverVersion ) {
86  if ( version_compare( $serverVersion, static::$minimumVersion ) < 0 ) {
87  return Status::newFatal(
88  static::$notMinimumVersionMessage, static::$minimumVersion, $serverVersion
89  );
90  }
91 
92  return Status::newGood();
93  }
94 
98  abstract public function getName();
99 
103  abstract public function isCompiled();
104 
111  public function checkPrerequisites() {
112  return Status::newGood();
113  }
114 
122  abstract public function getConnectForm();
123 
133  abstract public function submitConnectForm();
134 
143  public function getSettingsForm() {
144  return false;
145  }
146 
154  public function submitSettingsForm() {
155  return Status::newGood();
156  }
157 
166  abstract public function openConnection();
167 
174  abstract public function setupDatabase();
175 
187  public function getConnection() {
188  if ( $this->db ) {
189  return Status::newGood( $this->db );
190  }
191 
192  $status = $this->openConnection();
193  if ( $status->isOK() ) {
194  $this->db = $status->value;
195  // Enable autocommit
196  $this->db->clearFlag( DBO_TRX );
197  $this->db->commit( __METHOD__ );
198  }
199 
200  return $status;
201  }
202 
211  private function stepApplySourceFile(
212  $sourceFileMethod,
213  $stepName,
214  $tableThatMustNotExist = false
215  ) {
216  $status = $this->getConnection();
217  if ( !$status->isOK() ) {
218  return $status;
219  }
220  $this->db->selectDB( $this->getVar( 'wgDBname' ) );
221 
222  if ( $tableThatMustNotExist && $this->db->tableExists( $tableThatMustNotExist, __METHOD__ ) ) {
223  $status->warning( "config-$stepName-tables-exist" );
224  $this->enableLB();
225 
226  return $status;
227  }
228 
229  $this->db->setFlag( DBO_DDLMODE ); // For Oracle's handling of schema files
230  $this->db->begin( __METHOD__ );
231 
232  // @phan-suppress-next-line SecurityCheck-PathTraversal False positive
233  $error = $this->db->sourceFile(
234  call_user_func( [ $this, $sourceFileMethod ], $this->db )
235  );
236  if ( $error !== true ) {
237  $this->db->reportQueryError( $error, 0, '', __METHOD__ );
238  $this->db->rollback( __METHOD__ );
239  $status->fatal( "config-$stepName-tables-failed", $error );
240  } else {
241  $this->db->commit( __METHOD__ );
242  }
243  // Resume normal operations
244  if ( $status->isOK() ) {
245  $this->enableLB();
246  }
247 
248  return $status;
249  }
250 
257  public function createTables() {
258  return $this->stepApplySourceFile( 'getGeneratedSchemaPath', 'install', 'archive' );
259  }
260 
267  public function createManualTables() {
268  return $this->stepApplySourceFile( 'getSchemaPath', 'install-manual' );
269  }
270 
277  public function insertUpdateKeys() {
278  return $this->stepApplySourceFile( 'getUpdateKeysPath', 'updates', false );
279  }
280 
289  private function getSqlFilePath( $db, $filename ) {
290  global $IP;
291 
292  $dbmsSpecificFilePath = "$IP/maintenance/" . $db->getType() . "/$filename";
293  if ( file_exists( $dbmsSpecificFilePath ) ) {
294  return $dbmsSpecificFilePath;
295  } else {
296  return "$IP/maintenance/$filename";
297  }
298  }
299 
308  public function getSchemaPath( $db ) {
309  return $this->getSqlFilePath( $db, 'tables.sql' );
310  }
311 
319  public function getGeneratedSchemaPath( $db ) {
320  return $this->getSqlFilePath( $db, 'tables-generated.sql' );
321  }
322 
331  public function getUpdateKeysPath( $db ) {
332  return $this->getSqlFilePath( $db, 'update-keys.sql' );
333  }
334 
340  public function createExtensionTables() {
341  $status = $this->getConnection();
342  if ( !$status->isOK() ) {
343  return $status;
344  }
345 
346  // Now run updates to create tables for old extensions
347  $updater = DatabaseUpdater::newForDB( $this->db );
348  $updater->setAutoExtensionHookContainer( $this->parent->getAutoExtensionHookContainer() );
349  $updater->doUpdates( [ 'extensions' ] );
350 
351  return $status;
352  }
353 
359  abstract public function getLocalSettings();
360 
367  public function getSchemaVars() {
368  return [];
369  }
370 
379  public function setupSchemaVars() {
380  $status = $this->getConnection();
381  if ( $status->isOK() ) {
382  // @phan-suppress-next-line PhanUndeclaredMethod
383  $status->value->setSchemaVars( $this->getSchemaVars() );
384  } else {
385  $msg = __METHOD__ . ': unexpected error while establishing'
386  . ' a database connection with message: '
387  . $status->getMessage()->plain();
388  throw new MWException( $msg );
389  }
390  }
391 
397  public function enableLB() {
398  $status = $this->getConnection();
399  if ( !$status->isOK() ) {
400  throw new MWException( __METHOD__ . ': unexpected DB connection error' );
401  }
402  $connection = $status->value;
403 
404  $this->parent->resetMediaWikiServices( null, [
405  'DBLoadBalancerFactory' => static function () use ( $connection ) {
406  return LBFactorySingle::newFromConnection( $connection );
407  }
408  ] );
409  }
410 
417  public function doUpgrade() {
418  $this->setupSchemaVars();
419  $this->enableLB();
420 
421  $ret = true;
422  ob_start( [ $this, 'outputHandler' ] );
423  $up = DatabaseUpdater::newForDB( $this->db );
424  try {
425  $up->doUpdates();
426  $up->purgeCache();
427  } catch ( MWException $e ) {
428  echo "\nAn error occurred:\n";
429  echo $e->getText();
430  $ret = false;
431  } catch ( Exception $e ) {
432  echo "\nAn error occurred:\n";
433  echo $e->getMessage();
434  $ret = false;
435  }
436  ob_end_flush();
437 
438  return $ret;
439  }
440 
447  public function preInstall() {
448  }
449 
454  public function preUpgrade() {
455  }
456 
462  public function getGlobalNames() {
463  return $this->globalNames;
464  }
465 
472  public function __construct( $parent ) {
473  $this->parent = $parent;
474  }
475 
483  protected static function checkExtension( $name ) {
484  return extension_loaded( $name );
485  }
486 
492  public function getReadableName() {
493  // Messages: config-type-mysql, config-type-postgres, config-type-sqlite
494  return wfMessage( 'config-type-' . $this->getName() )->text();
495  }
496 
503  public function getGlobalDefaults() {
504  $defaults = [];
505  foreach ( $this->getGlobalNames() as $var ) {
506  if ( isset( $GLOBALS[$var] ) ) {
507  $defaults[$var] = $GLOBALS[$var];
508  }
509  }
510  return $defaults;
511  }
512 
517  public function getInternalDefaults() {
519  }
520 
527  public function getVar( $var, $default = null ) {
528  $defaults = $this->getGlobalDefaults();
529  $internal = $this->getInternalDefaults();
530  if ( isset( $defaults[$var] ) ) {
531  $default = $defaults[$var];
532  } elseif ( isset( $internal[$var] ) ) {
533  $default = $internal[$var];
534  }
535 
536  return $this->parent->getVar( $var, $default );
537  }
538 
544  public function setVar( $name, $value ) {
545  $this->parent->setVar( $name, $value );
546  }
547 
558  public function getTextBox( $var, $label, $attribs = [], $helpData = "" ) {
559  $name = $this->getName() . '_' . $var;
560  $value = $this->getVar( $var );
561  if ( !isset( $attribs ) ) {
562  $attribs = [];
563  }
564 
565  return $this->parent->getTextBox( [
566  'var' => $var,
567  'label' => $label,
568  'attribs' => $attribs,
569  'controlName' => $name,
570  'value' => $value,
571  'help' => $helpData
572  ] );
573  }
574 
586  public function getPasswordBox( $var, $label, $attribs = [], $helpData = "" ) {
587  $name = $this->getName() . '_' . $var;
588  $value = $this->getVar( $var );
589  if ( !isset( $attribs ) ) {
590  $attribs = [];
591  }
592 
593  return $this->parent->getPasswordBox( [
594  'var' => $var,
595  'label' => $label,
596  'attribs' => $attribs,
597  'controlName' => $name,
598  'value' => $value,
599  'help' => $helpData
600  ] );
601  }
602 
612  public function getCheckBox( $var, $label, $attribs = [], $helpData = "" ) {
613  $name = $this->getName() . '_' . $var;
614  $value = $this->getVar( $var );
615 
616  return $this->parent->getCheckBox( [
617  'var' => $var,
618  'label' => $label,
619  'attribs' => $attribs,
620  'controlName' => $name,
621  'value' => $value,
622  'help' => $helpData
623  ] );
624  }
625 
638  public function getRadioSet( $params ) {
639  $params['controlName'] = $this->getName() . '_' . $params['var'];
640  $params['value'] = $this->getVar( $params['var'] );
641 
642  return $this->parent->getRadioSet( $params );
643  }
644 
652  public function setVarsFromRequest( $varNames ) {
653  return $this->parent->setVarsFromRequest( $varNames, $this->getName() . '_' );
654  }
655 
667  public function needsUpgrade() {
668  $status = $this->getConnection();
669  if ( !$status->isOK() ) {
670  return false;
671  }
672 
673  try {
674  $this->db->selectDB( $this->getVar( 'wgDBname' ) );
675  } catch ( DBConnectionError $e ) {
676  // Don't catch DBConnectionError
677  throw $e;
678  } catch ( DBExpectedError $e ) {
679  return false;
680  }
681 
682  return $this->db->tableExists( 'cur', __METHOD__ ) ||
683  $this->db->tableExists( 'revision', __METHOD__ );
684  }
685 
691  public function getInstallUserBox() {
692  return Html::openElement( 'fieldset' ) .
693  Html::element( 'legend', [], wfMessage( 'config-db-install-account' )->text() ) .
694  // @phan-suppress-next-line SecurityCheck-DoubleEscaped taint cannot track the helpbox from the rest
695  $this->getTextBox(
696  '_InstallUser',
697  'config-db-username',
698  [ 'dir' => 'ltr' ],
699  $this->parent->getHelpBox( 'config-db-install-username' )
700  ) .
701  // @phan-suppress-next-line SecurityCheck-DoubleEscaped taint cannot track the helpbox from the rest
702  $this->getPasswordBox(
703  '_InstallPassword',
704  'config-db-password',
705  [ 'dir' => 'ltr' ],
706  $this->parent->getHelpBox( 'config-db-install-password' )
707  ) .
708  Html::closeElement( 'fieldset' );
709  }
710 
715  public function submitInstallUserBox() {
716  $this->setVarsFromRequest( [ '_InstallUser', '_InstallPassword' ] );
717 
718  return Status::newGood();
719  }
720 
728  public function getWebUserBox( $noCreateMsg = false ) {
729  $wrapperStyle = $this->getVar( '_SameAccount' ) ? 'display: none' : '';
730  $s = Html::openElement( 'fieldset' ) .
731  Html::element( 'legend', [], wfMessage( 'config-db-web-account' )->text() ) .
732  $this->getCheckBox(
733  '_SameAccount', 'config-db-web-account-same',
734  [ 'class' => 'hideShowRadio', 'rel' => 'dbOtherAccount' ]
735  ) .
736  Html::openElement( 'div', [ 'id' => 'dbOtherAccount', 'style' => $wrapperStyle ] ) .
737  $this->getTextBox( 'wgDBuser', 'config-db-username' ) .
738  $this->getPasswordBox( 'wgDBpassword', 'config-db-password' ) .
739  $this->parent->getHelpBox( 'config-db-web-help' );
740  if ( $noCreateMsg ) {
741  $s .= Html::warningBox( wfMessage( $noCreateMsg )->plain(), 'config-warning-box' );
742  } else {
743  $s .= $this->getCheckBox( '_CreateDBAccount', 'config-db-web-create' );
744  }
745  $s .= Html::closeElement( 'div' ) . Html::closeElement( 'fieldset' );
746 
747  return $s;
748  }
749 
755  public function submitWebUserBox() {
756  $this->setVarsFromRequest(
757  [ 'wgDBuser', 'wgDBpassword', '_SameAccount', '_CreateDBAccount' ]
758  );
759 
760  if ( $this->getVar( '_SameAccount' ) ) {
761  $this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
762  $this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
763  }
764 
765  if ( $this->getVar( '_CreateDBAccount' ) && strval( $this->getVar( 'wgDBpassword' ) ) == '' ) {
766  return Status::newFatal( 'config-db-password-empty', $this->getVar( 'wgDBuser' ) );
767  }
768 
769  return Status::newGood();
770  }
771 
778  public function populateInterwikiTable() {
779  $status = $this->getConnection();
780  if ( !$status->isOK() ) {
781  return $status;
782  }
783  $this->db->selectDB( $this->getVar( 'wgDBname' ) );
784 
785  if ( $this->db->selectRow( 'interwiki', '1', [], __METHOD__ ) ) {
786  $status->warning( 'config-install-interwiki-exists' );
787 
788  return $status;
789  }
790  global $IP;
791  AtEase::suppressWarnings();
792  $rows = file( "$IP/maintenance/interwiki.list",
793  FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES );
794  AtEase::restoreWarnings();
795  $interwikis = [];
796  if ( !$rows ) {
797  return Status::newFatal( 'config-install-interwiki-list' );
798  }
799  foreach ( $rows as $row ) {
800  $row = preg_replace( '/^\s*([^#]*?)\s*(#.*)?$/', '\\1', $row ); // strip comments - whee
801  if ( $row == "" ) {
802  continue;
803  }
804  $row .= "|";
805  $interwikis[] = array_combine(
806  [ 'iw_prefix', 'iw_url', 'iw_local', 'iw_api', 'iw_wikiid' ],
807  explode( '|', $row )
808  );
809  }
810  $this->db->insert( 'interwiki', $interwikis, __METHOD__ );
811 
812  return Status::newGood();
813  }
814 
815  public function outputHandler( $string ) {
816  return htmlspecialchars( $string );
817  }
818 }
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
if(!defined( 'MEDIAWIKI')) if(ini_get( 'mbstring.func_overload')) if(!defined( 'MW_ENTRY_POINT')) global $IP
Environment checks.
Definition: Setup.php:90
Base class for DBMS-specific installation helper classes.
getWebUserBox( $noCreateMsg=false)
Get a standard web-user fieldset.
submitWebUserBox()
Submit the form from getWebUserBox().
static checkExtension( $name)
Convenience function.
preUpgrade()
Allow DB installers a chance to make checks before upgrade.
__construct( $parent)
Construct and initialise parent.
enableLB()
Set up LBFactory so that wfGetDB() etc.
getUpdateKeysPath( $db)
Return a path to the DBMS-specific update key file, otherwise default to update-keys....
getGlobalNames()
Get an array of MW configuration globals that will be configured by this class.
getReadableName()
Get the internationalised name for this DBMS.
WebInstaller $parent
The Installer object.
submitSettingsForm()
Set variables based on the request array, assuming it was submitted via the form return by getSetting...
Database $db
The database connection.
static meetsMinimumRequirement( $serverVersion)
Whether the provided version meets the necessary requirements for this type.
createExtensionTables()
Create the tables for each extension the user enabled.
getGeneratedSchemaPath( $db)
Return a path to the DBMS-specific automatically generated schema file.
static string $notMinimumVersionMessage
Set by subclasses.
setupDatabase()
Create the database and return a Status object indicating success or failure.
getPasswordBox( $var, $label, $attribs=[], $helpData="")
Get a labelled password box to configure a local variable.
setVarsFromRequest( $varNames)
Convenience function to set variables based on form data.
getSettingsForm()
Get HTML for a web form that retrieves settings used for installation.
static string $minimumVersion
Set by subclasses.
needsUpgrade()
Determine whether an existing installation of MediaWiki is present in the configured administrative c...
getSqlFilePath( $db, $filename)
Return a path to the DBMS-specific SQL file if it exists, otherwise default SQL file.
submitConnectForm()
Set variables based on the request array, assuming it was submitted via the form returned by getConne...
getCheckBox( $var, $label, $attribs=[], $helpData="")
Get a labelled checkbox to configure a local boolean variable.
getSchemaPath( $db)
Return a path to the DBMS-specific schema file, otherwise default to tables.sql.
stepApplySourceFile( $sourceFileMethod, $stepName, $tableThatMustNotExist=false)
Apply a SQL source file to the database as part of running an installation step.
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....
getVar( $var, $default=null)
Get a variable, taking local defaults into account.
getTextBox( $var, $label, $attribs=[], $helpData="")
Get a labelled text box to configure a local variable.
preInstall()
Allow DB installers a chance to make last-minute changes before installation occurs.
createTables()
Create database tables from scratch from the automatically generated file.
populateInterwikiTable()
Common function for databases that don't understand the MySQLish syntax of interwiki....
array $internalDefaults
Internal variables for installation.
doUpgrade()
Perform database upgrades.
checkPrerequisites()
Checks for installation prerequisites other than those checked by isCompiled()
getInternalDefaults()
Get a name=>value map of internal variables used during installation.
setVar( $name, $value)
Convenience alias for $this->parent->setVar()
submitInstallUserBox()
Submit a standard install user fieldset.
getConnectForm()
Get HTML for a web form that configures this database.
openConnection()
Open a connection to the database using the administrative user/password currently defined in the ses...
getName()
Return the internal name, e.g.
array $globalNames
Array of MW configuration globals this class uses.
getLocalSettings()
Get the DBMS-specific options for LocalSettings.php generation.
getGlobalDefaults()
Get a name=>value map of MW configuration globals for the default values.
createManualTables()
Create database tables from scratch.
getInstallUserBox()
Get a standard install-user fieldset.
insertUpdateKeys()
Insert update keys into table to prevent running unneeded updates.
setupSchemaVars()
Set appropriate schema variables in the current database connection.
getRadioSet( $params)
Get a set of labelled radio buttons.
static newForDB(IMaintainableDatabase $db, $shared=false, Maintenance $maintenance=null)
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:236
static warningBox( $html, $className='')
Return a warning box.
Definition: Html.php:775
static openElement( $element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing '/'...
Definition: Html.php:256
static closeElement( $element)
Returns "</$element>".
Definition: Html.php:320
MediaWiki exception.
Definition: MWException.php:31
getText()
Get the text to display when reporting the error on the command line.
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:70
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:82
Base class for the more common types of database errors.
An LBFactory class that always returns a single database object.
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:40
getType()
Get the RDBMS type of the server (e.g.
foreach( $mmfl['setupFiles'] as $fileName) if( $queue) if(empty( $mmfl['quiet'])) $s
const DBO_DDLMODE
Definition: defines.php:16
const DBO_TRX
Definition: defines.php:12