MediaWiki REL1_34
DatabaseInstaller.php
Go to the documentation of this file.
1<?php
30
37abstract 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}
$GLOBALS['IP']
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
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.
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.
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.
stepApplySourceFile( $sourceFileMethod, $stepName, $archiveTableMustNotExist=false)
Apply a SQL source file to the database as part of running an installation step.
preInstall()
Allow DB installers a chance to make last-minute changes before installation occurs.
createTables()
Create database tables from scratch.
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.
getInstallUserBox()
Get a standard install-user fieldset.
insertUpdateKeys()
Insert update keys into table to prevent running unneded updates.
setupSchemaVars()
Set appropriate schema variables in the current database connection.
getRadioSet( $params)
Get a set of labelled radio buttons.
MediaWiki exception.
getText()
Get the text to display when reporting the error on the command line.
MediaWikiServices is the service locator for the application scope of MediaWiki.
Class for the core installer web interface.
Base class for the more common types of database errors.
Relational database abstraction object.
Definition Database.php:49
An LBFactory class that always returns a single database object.
$IP
Definition update.php:3
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:38
getType()
Get the type of the DBMS (e.g.
const DBO_DDLMODE
Definition defines.php:16
const DBO_TRX
Definition defines.php:12