MediaWiki REL1_37
DatabaseInstaller.php
Go to the documentation of this file.
1<?php
29
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
110 public function checkPrerequisites() {
111 return Status::newGood();
112 }
113
121 abstract public function getConnectForm();
122
132 abstract public function submitConnectForm();
133
142 public function getSettingsForm() {
143 return false;
144 }
145
153 public function submitSettingsForm() {
154 return Status::newGood();
155 }
156
165 abstract public function openConnection();
166
173 abstract public function setupDatabase();
174
186 public function getConnection() {
187 if ( $this->db ) {
188 return Status::newGood( $this->db );
189 }
190
191 $status = $this->openConnection();
192 if ( $status->isOK() ) {
193 $this->db = $status->value;
194 // Enable autocommit
195 $this->db->clearFlag( DBO_TRX );
196 $this->db->commit( __METHOD__ );
197 }
198
199 return $status;
200 }
201
210 private function stepApplySourceFile(
211 $sourceFileMethod,
212 $stepName,
213 $tableThatMustNotExist = false
214 ) {
215 $status = $this->getConnection();
216 if ( !$status->isOK() ) {
217 return $status;
218 }
219 $this->db->selectDB( $this->getVar( 'wgDBname' ) );
220
221 if ( $tableThatMustNotExist && $this->db->tableExists( $tableThatMustNotExist, __METHOD__ ) ) {
222 $status->warning( "config-$stepName-tables-exist" );
223 $this->enableLB();
224
225 return $status;
226 }
227
228 $this->db->setFlag( DBO_DDLMODE ); // For Oracle's handling of schema files
229 $this->db->begin( __METHOD__ );
230
231 // @phan-suppress-next-line SecurityCheck-PathTraversal False positive
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 echo "\nAn error occurred:\n";
428 echo $e->getText();
429 $ret = false;
430 } catch ( Exception $e ) {
431 echo "\nAn error occurred:\n";
432 echo $e->getMessage();
433 $ret = false;
434 }
435 ob_end_flush();
436
437 return $ret;
438 }
439
446 public function preInstall() {
447 }
448
453 public function preUpgrade() {
454 }
455
461 public function getGlobalNames() {
462 return $this->globalNames;
463 }
464
471 public function __construct( $parent ) {
472 $this->parent = $parent;
473 }
474
482 protected static function checkExtension( $name ) {
483 return extension_loaded( $name );
484 }
485
491 public function getReadableName() {
492 // Messages: config-type-mysql, config-type-postgres, config-type-sqlite
493 return wfMessage( 'config-type-' . $this->getName() )->text();
494 }
495
501 public function getGlobalDefaults() {
502 $defaults = [];
503 foreach ( $this->getGlobalNames() as $var ) {
504 if ( isset( $GLOBALS[$var] ) ) {
505 $defaults[$var] = $GLOBALS[$var];
506 }
507 }
508 return $defaults;
509 }
510
515 public function getInternalDefaults() {
517 }
518
525 public function getVar( $var, $default = null ) {
526 $defaults = $this->getGlobalDefaults();
527 $internal = $this->getInternalDefaults();
528 if ( isset( $defaults[$var] ) ) {
529 $default = $defaults[$var];
530 } elseif ( isset( $internal[$var] ) ) {
531 $default = $internal[$var];
532 }
533
534 return $this->parent->getVar( $var, $default );
535 }
536
542 public function setVar( $name, $value ) {
543 $this->parent->setVar( $name, $value );
544 }
545
556 public function getTextBox( $var, $label, $attribs = [], $helpData = "" ) {
557 $name = $this->getName() . '_' . $var;
558 $value = $this->getVar( $var );
559 if ( !isset( $attribs ) ) {
560 $attribs = [];
561 }
562
563 return $this->parent->getTextBox( [
564 'var' => $var,
565 'label' => $label,
566 'attribs' => $attribs,
567 'controlName' => $name,
568 'value' => $value,
569 'help' => $helpData
570 ] );
571 }
572
584 public function getPasswordBox( $var, $label, $attribs = [], $helpData = "" ) {
585 $name = $this->getName() . '_' . $var;
586 $value = $this->getVar( $var );
587 if ( !isset( $attribs ) ) {
588 $attribs = [];
589 }
590
591 return $this->parent->getPasswordBox( [
592 'var' => $var,
593 'label' => $label,
594 'attribs' => $attribs,
595 'controlName' => $name,
596 'value' => $value,
597 'help' => $helpData
598 ] );
599 }
600
610 public function getCheckBox( $var, $label, $attribs = [], $helpData = "" ) {
611 $name = $this->getName() . '_' . $var;
612 $value = $this->getVar( $var );
613
614 return $this->parent->getCheckBox( [
615 'var' => $var,
616 'label' => $label,
617 'attribs' => $attribs,
618 'controlName' => $name,
619 'value' => $value,
620 'help' => $helpData
621 ] );
622 }
623
636 public function getRadioSet( $params ) {
637 $params['controlName'] = $this->getName() . '_' . $params['var'];
638 $params['value'] = $this->getVar( $params['var'] );
639
640 return $this->parent->getRadioSet( $params );
641 }
642
650 public function setVarsFromRequest( $varNames ) {
651 return $this->parent->setVarsFromRequest( $varNames, $this->getName() . '_' );
652 }
653
665 public function needsUpgrade() {
666 $status = $this->getConnection();
667 if ( !$status->isOK() ) {
668 return false;
669 }
670
671 try {
672 $this->db->selectDB( $this->getVar( 'wgDBname' ) );
673 } catch ( DBConnectionError $e ) {
674 // Don't catch DBConnectionError
675 throw $e;
676 } catch ( DBExpectedError $e ) {
677 return false;
678 }
679
680 return $this->db->tableExists( 'cur', __METHOD__ ) ||
681 $this->db->tableExists( 'revision', __METHOD__ );
682 }
683
689 public function getInstallUserBox() {
690 return Html::openElement( 'fieldset' ) .
691 Html::element( 'legend', [], wfMessage( 'config-db-install-account' )->text() ) .
692 // @phan-suppress-next-line SecurityCheck-DoubleEscaped taint cannot track the helpbox from the rest
693 $this->getTextBox(
694 '_InstallUser',
695 'config-db-username',
696 [ 'dir' => 'ltr' ],
697 $this->parent->getHelpBox( 'config-db-install-username' )
698 ) .
699 // @phan-suppress-next-line SecurityCheck-DoubleEscaped taint cannot track the helpbox from the rest
700 $this->getPasswordBox(
701 '_InstallPassword',
702 'config-db-password',
703 [ 'dir' => 'ltr' ],
704 $this->parent->getHelpBox( 'config-db-install-password' )
705 ) .
706 Html::closeElement( 'fieldset' );
707 }
708
713 public function submitInstallUserBox() {
714 $this->setVarsFromRequest( [ '_InstallUser', '_InstallPassword' ] );
715
716 return Status::newGood();
717 }
718
726 public function getWebUserBox( $noCreateMsg = false ) {
727 $wrapperStyle = $this->getVar( '_SameAccount' ) ? 'display: none' : '';
728 $s = Html::openElement( 'fieldset' ) .
729 Html::element( 'legend', [], wfMessage( 'config-db-web-account' )->text() ) .
730 $this->getCheckBox(
731 '_SameAccount', 'config-db-web-account-same',
732 [ 'class' => 'hideShowRadio', 'rel' => 'dbOtherAccount' ]
733 ) .
734 Html::openElement( 'div', [ 'id' => 'dbOtherAccount', 'style' => $wrapperStyle ] ) .
735 $this->getTextBox( 'wgDBuser', 'config-db-username' ) .
736 $this->getPasswordBox( 'wgDBpassword', 'config-db-password' ) .
737 $this->parent->getHelpBox( 'config-db-web-help' );
738 if ( $noCreateMsg ) {
739 $s .= Html::warningBox( wfMessage( $noCreateMsg )->plain(), 'config-warning-box' );
740 } else {
741 $s .= $this->getCheckBox( '_CreateDBAccount', 'config-db-web-create' );
742 }
743 $s .= Html::closeElement( 'div' ) . Html::closeElement( 'fieldset' );
744
745 return $s;
746 }
747
753 public function submitWebUserBox() {
754 $this->setVarsFromRequest(
755 [ 'wgDBuser', 'wgDBpassword', '_SameAccount', '_CreateDBAccount' ]
756 );
757
758 if ( $this->getVar( '_SameAccount' ) ) {
759 $this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
760 $this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
761 }
762
763 if ( $this->getVar( '_CreateDBAccount' ) && strval( $this->getVar( 'wgDBpassword' ) ) == '' ) {
764 return Status::newFatal( 'config-db-password-empty', $this->getVar( 'wgDBuser' ) );
765 }
766
767 return Status::newGood();
768 }
769
776 public function populateInterwikiTable() {
777 $status = $this->getConnection();
778 if ( !$status->isOK() ) {
779 return $status;
780 }
781 $this->db->selectDB( $this->getVar( 'wgDBname' ) );
782
783 if ( $this->db->selectRow( 'interwiki', '1', [], __METHOD__ ) ) {
784 $status->warning( 'config-install-interwiki-exists' );
785
786 return $status;
787 }
788 global $IP;
789 Wikimedia\suppressWarnings();
790 $rows = file( "$IP/maintenance/interwiki.list",
791 FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES );
792 Wikimedia\restoreWarnings();
793 $interwikis = [];
794 if ( !$rows ) {
795 return Status::newFatal( 'config-install-interwiki-list' );
796 }
797 foreach ( $rows as $row ) {
798 $row = preg_replace( '/^\s*([^#]*?)\s*(#.*)?$/', '\\1', $row ); // strip comments - whee
799 if ( $row == "" ) {
800 continue;
801 }
802 $row .= "|";
803 $interwikis[] = array_combine(
804 [ 'iw_prefix', 'iw_url', 'iw_local', 'iw_api', 'iw_wikiid' ],
805 explode( '|', $row )
806 );
807 }
808 $this->db->insert( 'interwiki', $interwikis, __METHOD__ );
809
810 return Status::newGood();
811 }
812
813 public function outputHandler( $string ) {
814 return htmlspecialchars( $string );
815 }
816}
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
$IP
Definition WebStart.php:49
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.
MediaWiki exception.
getText()
Get the text to display when reporting the error on the command line.
Class for the core installer web interface.
Base class for the more common types of database errors.
Relational database abstraction object.
Definition Database.php:52
An LBFactory class that always returns a single database object.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:38
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