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 );
230  $this->db->begin( __METHOD__ );
231 
232  $error = $this->db->sourceFile(
233  call_user_func( [ $this, $sourceFileMethod ], $this->db )
234  );
235  if ( $error !== true ) {
236  $this->db->reportQueryError( $error, 0, '', __METHOD__ );
237  $this->db->rollback( __METHOD__ );
238  $status->fatal( "config-$stepName-tables-failed", $error );
239  } else {
240  $this->db->commit( __METHOD__ );
241  }
242  // Resume normal operations
243  if ( $status->isOK() ) {
244  $this->enableLB();
245  }
246 
247  return $status;
248  }
249 
256  public function createTables() {
257  return $this->stepApplySourceFile( 'getGeneratedSchemaPath', 'install', 'archive' );
258  }
259 
266  public function createManualTables() {
267  return $this->stepApplySourceFile( 'getSchemaPath', 'install-manual' );
268  }
269 
276  public function insertUpdateKeys() {
277  return $this->stepApplySourceFile( 'getUpdateKeysPath', 'updates', false );
278  }
279 
288  private function getSqlFilePath( $db, $filename ) {
289  global $IP;
290 
291  $dbmsSpecificFilePath = "$IP/maintenance/" . $db->getType() . "/$filename";
292  if ( file_exists( $dbmsSpecificFilePath ) ) {
293  return $dbmsSpecificFilePath;
294  } else {
295  return "$IP/maintenance/$filename";
296  }
297  }
298 
307  public function getSchemaPath( $db ) {
308  return $this->getSqlFilePath( $db, 'tables.sql' );
309  }
310 
318  public function getGeneratedSchemaPath( $db ) {
319  return $this->getSqlFilePath( $db, 'tables-generated.sql' );
320  }
321 
330  public function getUpdateKeysPath( $db ) {
331  return $this->getSqlFilePath( $db, 'update-keys.sql' );
332  }
333 
339  public function createExtensionTables() {
340  $status = $this->getConnection();
341  if ( !$status->isOK() ) {
342  return $status;
343  }
344 
345  // Now run updates to create tables for old extensions
346  $updater = DatabaseUpdater::newForDB( $this->db );
347  $updater->setAutoExtensionHookContainer( $this->parent->getAutoExtensionHookContainer() );
348  $updater->doUpdates( [ 'extensions' ] );
349 
350  return $status;
351  }
352 
358  abstract public function getLocalSettings();
359 
366  public function getSchemaVars() {
367  return [];
368  }
369 
378  public function setupSchemaVars() {
379  $status = $this->getConnection();
380  if ( $status->isOK() ) {
381  // @phan-suppress-next-line PhanUndeclaredMethod
382  $status->value->setSchemaVars( $this->getSchemaVars() );
383  } else {
384  $msg = __METHOD__ . ': unexpected error while establishing'
385  . ' a database connection with message: '
386  . $status->getMessage()->plain();
387  throw new MWException( $msg );
388  }
389  }
390 
396  public function enableLB() {
397  $status = $this->getConnection();
398  if ( !$status->isOK() ) {
399  throw new MWException( __METHOD__ . ': unexpected DB connection error' );
400  }
401  $connection = $status->value;
402 
403  $this->parent->resetMediaWikiServices( null, [
404  'DBLoadBalancerFactory' => static function () use ( $connection ) {
405  return LBFactorySingle::newFromConnection( $connection );
406  }
407  ] );
408  }
409 
416  public function doUpgrade() {
417  $this->setupSchemaVars();
418  $this->enableLB();
419 
420  $ret = true;
421  ob_start( [ $this, 'outputHandler' ] );
422  $up = DatabaseUpdater::newForDB( $this->db );
423  try {
424  $up->doUpdates();
425  $up->purgeCache();
426  } catch ( MWException $e ) {
427  // TODO: Remove special casing in favour of MWExceptionRenderer
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:91
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...
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.
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:29
getText()
Format plain text message for the current exception object.
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:73
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:85
Base class for the more common types of database errors.
Manage a single hardcoded database connection.
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