MediaWiki REL1_38
DatabaseInstaller.php
Go to the documentation of this file.
1<?php
24use Wikimedia\AtEase\AtEase;
30
38abstract 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.
$IP
Definition WebStart.php:51
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:51
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