MediaWiki REL1_35
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 $archiveTableMustNotExist = false
214 ) {
215 $status = $this->getConnection();
216 if ( !$status->isOK() ) {
217 return $status;
218 }
219 $this->db->selectDB( $this->getVar( 'wgDBname' ) );
220
221 if ( $archiveTableMustNotExist && $this->db->tableExists( 'archive', __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 $error = $this->db->sourceFile(
232 call_user_func( [ $this, $sourceFileMethod ], $this->db )
233 );
234 if ( $error !== true ) {
235 $this->db->reportQueryError( $error, 0, '', __METHOD__ );
236 $this->db->rollback( __METHOD__ );
237 $status->fatal( "config-$stepName-tables-failed", $error );
238 } else {
239 $this->db->commit( __METHOD__ );
240 }
241 // Resume normal operations
242 if ( $status->isOK() ) {
243 $this->enableLB();
244 }
245
246 return $status;
247 }
248
255 public function createTables() {
256 return $this->stepApplySourceFile( 'getGeneratedSchemaPath', 'install', true );
257 }
258
265 public function createManualTables() {
266 // TODO: Set "archiveTableMustNotExist" to "false" when archive table is migrated to tables.json
267 return $this->stepApplySourceFile( 'getSchemaPath', 'install-manual', true );
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' => 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
555 public function getTextBox( $var, $label, $attribs = [], $helpData = "" ) {
556 $name = $this->getName() . '_' . $var;
557 $value = $this->getVar( $var );
558 if ( !isset( $attribs ) ) {
559 $attribs = [];
560 }
561
562 return $this->parent->getTextBox( [
563 'var' => $var,
564 'label' => $label,
565 'attribs' => $attribs,
566 'controlName' => $name,
567 'value' => $value,
568 'help' => $helpData
569 ] );
570 }
571
582 public function getPasswordBox( $var, $label, $attribs = [], $helpData = "" ) {
583 $name = $this->getName() . '_' . $var;
584 $value = $this->getVar( $var );
585 if ( !isset( $attribs ) ) {
586 $attribs = [];
587 }
588
589 return $this->parent->getPasswordBox( [
590 'var' => $var,
591 'label' => $label,
592 'attribs' => $attribs,
593 'controlName' => $name,
594 'value' => $value,
595 'help' => $helpData
596 ] );
597 }
598
608 public function getCheckBox( $var, $label, $attribs = [], $helpData = "" ) {
609 $name = $this->getName() . '_' . $var;
610 $value = $this->getVar( $var );
611
612 return $this->parent->getCheckBox( [
613 'var' => $var,
614 'label' => $label,
615 'attribs' => $attribs,
616 'controlName' => $name,
617 'value' => $value,
618 'help' => $helpData
619 ] );
620 }
621
634 public function getRadioSet( $params ) {
635 $params['controlName'] = $this->getName() . '_' . $params['var'];
636 $params['value'] = $this->getVar( $params['var'] );
637
638 return $this->parent->getRadioSet( $params );
639 }
640
648 public function setVarsFromRequest( $varNames ) {
649 return $this->parent->setVarsFromRequest( $varNames, $this->getName() . '_' );
650 }
651
663 public function needsUpgrade() {
664 $status = $this->getConnection();
665 if ( !$status->isOK() ) {
666 return false;
667 }
668
669 try {
670 $this->db->selectDB( $this->getVar( 'wgDBname' ) );
671 } catch ( DBConnectionError $e ) {
672 // Don't catch DBConnectionError
673 throw $e;
674 } catch ( DBExpectedError $e ) {
675 return false;
676 }
677
678 return $this->db->tableExists( 'cur', __METHOD__ ) ||
679 $this->db->tableExists( 'revision', __METHOD__ );
680 }
681
687 public function getInstallUserBox() {
688 return Html::openElement( 'fieldset' ) .
689 Html::element( 'legend', [], wfMessage( 'config-db-install-account' )->text() ) .
690 $this->getTextBox(
691 '_InstallUser',
692 'config-db-username',
693 [ 'dir' => 'ltr' ],
694 $this->parent->getHelpBox( 'config-db-install-username' )
695 ) .
696 $this->getPasswordBox(
697 '_InstallPassword',
698 'config-db-password',
699 [ 'dir' => 'ltr' ],
700 $this->parent->getHelpBox( 'config-db-install-password' )
701 ) .
702 Html::closeElement( 'fieldset' );
703 }
704
709 public function submitInstallUserBox() {
710 $this->setVarsFromRequest( [ '_InstallUser', '_InstallPassword' ] );
711
712 return Status::newGood();
713 }
714
722 public function getWebUserBox( $noCreateMsg = false ) {
723 $wrapperStyle = $this->getVar( '_SameAccount' ) ? 'display: none' : '';
724 $s = Html::openElement( 'fieldset' ) .
725 Html::element( 'legend', [], wfMessage( 'config-db-web-account' )->text() ) .
726 $this->getCheckBox(
727 '_SameAccount', 'config-db-web-account-same',
728 [ 'class' => 'hideShowRadio', 'rel' => 'dbOtherAccount' ]
729 ) .
730 Html::openElement( 'div', [ 'id' => 'dbOtherAccount', 'style' => $wrapperStyle ] ) .
731 $this->getTextBox( 'wgDBuser', 'config-db-username' ) .
732 $this->getPasswordBox( 'wgDBpassword', 'config-db-password' ) .
733 $this->parent->getHelpBox( 'config-db-web-help' );
734 if ( $noCreateMsg ) {
735 $s .= Html::warningBox( wfMessage( $noCreateMsg )->plain(), 'config-warning-box' );
736 } else {
737 $s .= $this->getCheckBox( '_CreateDBAccount', 'config-db-web-create' );
738 }
739 $s .= Html::closeElement( 'div' ) . Html::closeElement( 'fieldset' );
740
741 return $s;
742 }
743
749 public function submitWebUserBox() {
750 $this->setVarsFromRequest(
751 [ 'wgDBuser', 'wgDBpassword', '_SameAccount', '_CreateDBAccount' ]
752 );
753
754 if ( $this->getVar( '_SameAccount' ) ) {
755 $this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
756 $this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
757 }
758
759 if ( $this->getVar( '_CreateDBAccount' ) && strval( $this->getVar( 'wgDBpassword' ) ) == '' ) {
760 return Status::newFatal( 'config-db-password-empty', $this->getVar( 'wgDBuser' ) );
761 }
762
763 return Status::newGood();
764 }
765
772 public function populateInterwikiTable() {
773 $status = $this->getConnection();
774 if ( !$status->isOK() ) {
775 return $status;
776 }
777 $this->db->selectDB( $this->getVar( 'wgDBname' ) );
778
779 if ( $this->db->selectRow( 'interwiki', '1', [], __METHOD__ ) ) {
780 $status->warning( 'config-install-interwiki-exists' );
781
782 return $status;
783 }
784 global $IP;
785 Wikimedia\suppressWarnings();
786 $rows = file( "$IP/maintenance/interwiki.list",
787 FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES );
788 Wikimedia\restoreWarnings();
789 $interwikis = [];
790 if ( !$rows ) {
791 return Status::newFatal( 'config-install-interwiki-list' );
792 }
793 foreach ( $rows as $row ) {
794 $row = preg_replace( '/^\s*([^#]*?)\s*(#.*)?$/', '\\1', $row ); // strip comments - whee
795 if ( $row == "" ) {
796 continue;
797 }
798 $row .= "|";
799 $interwikis[] = array_combine(
800 [ 'iw_prefix', 'iw_url', 'iw_local', 'iw_api', 'iw_wikiid' ],
801 explode( '|', $row )
802 );
803 }
804 $this->db->insert( 'interwiki', $interwikis, __METHOD__ );
805
806 return Status::newGood();
807 }
808
809 public function outputHandler( $string ) {
810 return htmlspecialchars( $string );
811 }
812}
$GLOBALS['IP']
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 Stable to override.
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 Stable to override.
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 from the automatically generated file Stable to override.
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() Stable to override.
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 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.
Class for the core installer web interface.
Base class for the more common types of database errors.
Relational database abstraction object.
Definition Database.php:50
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 type of the DBMS (e.g.
const DBO_DDLMODE
Definition defines.php:16
const DBO_TRX
Definition defines.php:12