MediaWiki REL1_41
DatabaseInstaller.php
Go to the documentation of this file.
1<?php
26use Wikimedia\AtEase\AtEase;
33
41abstract class DatabaseInstaller {
42
48 public $parent;
49
53 public static $minimumVersion;
54
58 protected static $notMinimumVersionMessage;
59
65 public $db = null;
66
72 protected $internalDefaults = [];
73
79 protected $globalNames = [];
80
88 public static function meetsMinimumRequirement( IDatabase $conn ) {
89 $serverVersion = $conn->getServerVersion();
90 if ( version_compare( $serverVersion, static::$minimumVersion ) < 0 ) {
91 return Status::newFatal(
92 static::$notMinimumVersionMessage, static::$minimumVersion, $serverVersion
93 );
94 }
95
96 return Status::newGood();
97 }
98
102 abstract public function getName();
103
107 abstract public function isCompiled();
108
115 public function checkPrerequisites() {
116 return Status::newGood();
117 }
118
126 abstract public function getConnectForm();
127
137 abstract public function submitConnectForm();
138
147 public function getSettingsForm() {
148 return false;
149 }
150
158 public function submitSettingsForm() {
159 return Status::newGood();
160 }
161
170 abstract public function openConnection();
171
178 abstract public function setupDatabase();
179
191 public function getConnection() {
192 if ( $this->db ) {
193 return Status::newGood( $this->db );
194 }
195
196 $status = $this->openConnection();
197 if ( $status->isOK() ) {
198 $this->db = $status->value;
199 // Enable autocommit
200 $this->db->clearFlag( DBO_TRX );
201 $this->db->commit( __METHOD__ );
202 }
203
204 return $status;
205 }
206
215 private function stepApplySourceFile(
216 $sourceFileMethod,
217 $stepName,
218 $tableThatMustNotExist = false
219 ) {
220 $status = $this->getConnection();
221 if ( !$status->isOK() ) {
222 return $status;
223 }
224 $this->selectDatabase( $this->db, $this->getVar( 'wgDBname' ) );
225
226 if ( $tableThatMustNotExist && $this->db->tableExists( $tableThatMustNotExist, __METHOD__ ) ) {
227 $status->warning( "config-$stepName-tables-exist" );
228 $this->enableLB();
229
230 return $status;
231 }
232
233 $this->db->setFlag( DBO_DDLMODE );
234 $this->db->begin( __METHOD__ );
235
236 $error = $this->db->sourceFile(
237 call_user_func( [ $this, $sourceFileMethod ], $this->db )
238 );
239 if ( $error !== true ) {
240 $this->db->reportQueryError( $error, 0, '', __METHOD__ );
241 $this->db->rollback( __METHOD__ );
242 $status->fatal( "config-$stepName-tables-failed", $error );
243 } else {
244 $this->db->commit( __METHOD__ );
245 }
246 // Resume normal operations
247 if ( $status->isOK() ) {
248 $this->enableLB();
249 }
250
251 return $status;
252 }
253
260 public function createTables() {
261 return $this->stepApplySourceFile( 'getGeneratedSchemaPath', 'install', 'archive' );
262 }
263
270 public function createManualTables() {
271 return $this->stepApplySourceFile( 'getSchemaPath', 'install-manual' );
272 }
273
280 public function insertUpdateKeys() {
281 return $this->stepApplySourceFile( 'getUpdateKeysPath', 'updates', false );
282 }
283
292 private function getSqlFilePath( $db, $filename ) {
293 global $IP;
294
295 $dbmsSpecificFilePath = "$IP/maintenance/" . $db->getType() . "/$filename";
296 if ( file_exists( $dbmsSpecificFilePath ) ) {
297 return $dbmsSpecificFilePath;
298 } else {
299 return "$IP/maintenance/$filename";
300 }
301 }
302
311 public function getSchemaPath( $db ) {
312 return $this->getSqlFilePath( $db, 'tables.sql' );
313 }
314
322 public function getGeneratedSchemaPath( $db ) {
323 return $this->getSqlFilePath( $db, 'tables-generated.sql' );
324 }
325
334 public function getUpdateKeysPath( $db ) {
335 return $this->getSqlFilePath( $db, 'update-keys.sql' );
336 }
337
343 public function createExtensionTables() {
344 $status = $this->getConnection();
345 if ( !$status->isOK() ) {
346 return $status;
347 }
348
349 // Now run updates to create tables for old extensions
350 $updater = DatabaseUpdater::newForDB( $this->db );
351 $updater->setAutoExtensionHookContainer( $this->parent->getAutoExtensionHookContainer() );
352 $updater->doUpdates( [ 'extensions' ] );
353
354 return $status;
355 }
356
362 abstract public function getLocalSettings();
363
370 public function getSchemaVars() {
371 return [];
372 }
373
382 public function setupSchemaVars() {
383 $status = $this->getConnection();
384 if ( $status->isOK() ) {
385 // @phan-suppress-next-line PhanUndeclaredMethod
386 $status->value->setSchemaVars( $this->getSchemaVars() );
387 } else {
388 $msg = __METHOD__ . ': unexpected error while establishing'
389 . ' a database connection with message: '
390 . $status->getMessage()->plain();
391 throw new MWException( $msg );
392 }
393 }
394
400 public function enableLB() {
401 $status = $this->getConnection();
402 if ( !$status->isOK() ) {
403 throw new MWException( __METHOD__ . ': unexpected DB connection error' );
404 }
405 $connection = $status->value;
406
407 $this->parent->resetMediaWikiServices( null, [
408 'DBLoadBalancerFactory' => static function () use ( $connection ) {
409 return LBFactorySingle::newFromConnection( $connection );
410 }
411 ] );
412 }
413
420 public function doUpgrade() {
421 $this->setupSchemaVars();
422 $this->enableLB();
423
424 $ret = true;
425 ob_start( [ $this, 'outputHandler' ] );
426 $up = DatabaseUpdater::newForDB( $this->db );
427 try {
428 $up->doUpdates();
429 $up->purgeCache();
430 } catch ( MWException $e ) {
431 // TODO: Remove special casing in favour of MWExceptionRenderer
432 echo "\nAn error occurred:\n";
433 echo $e->getText();
434 $ret = false;
435 } catch ( Exception $e ) {
436 echo "\nAn error occurred:\n";
437 echo $e->getMessage();
438 $ret = false;
439 }
440 ob_end_flush();
441
442 return $ret;
443 }
444
451 public function preInstall() {
452 }
453
458 public function preUpgrade() {
459 }
460
466 public function getGlobalNames() {
467 return $this->globalNames;
468 }
469
476 public function __construct( $parent ) {
477 $this->parent = $parent;
478 }
479
487 protected static function checkExtension( $name ) {
488 return extension_loaded( $name );
489 }
490
496 public function getReadableName() {
497 // Messages: config-type-mysql, config-type-postgres, config-type-sqlite
498 return wfMessage( 'config-type-' . $this->getName() )->text();
499 }
500
507 public function getGlobalDefaults() {
508 $defaults = [];
509 foreach ( $this->getGlobalNames() as $var ) {
510 if ( isset( $GLOBALS[$var] ) ) {
511 $defaults[$var] = $GLOBALS[$var];
512 }
513 }
514 return $defaults;
515 }
516
521 public function getInternalDefaults() {
523 }
524
531 public function getVar( $var, $default = null ) {
532 $defaults = $this->getGlobalDefaults();
533 $internal = $this->getInternalDefaults();
534 if ( isset( $defaults[$var] ) ) {
535 $default = $defaults[$var];
536 } elseif ( isset( $internal[$var] ) ) {
537 $default = $internal[$var];
538 }
539
540 return $this->parent->getVar( $var, $default );
541 }
542
548 public function setVar( $name, $value ) {
549 $this->parent->setVar( $name, $value );
550 }
551
562 public function getTextBox( $var, $label, $attribs = [], $helpData = "" ) {
563 $name = $this->getName() . '_' . $var;
564 $value = $this->getVar( $var );
565 if ( !isset( $attribs ) ) {
566 $attribs = [];
567 }
568
569 return $this->parent->getTextBox( [
570 'var' => $var,
571 'label' => $label,
572 'attribs' => $attribs,
573 'controlName' => $name,
574 'value' => $value,
575 'help' => $helpData
576 ] );
577 }
578
590 public function getPasswordBox( $var, $label, $attribs = [], $helpData = "" ) {
591 $name = $this->getName() . '_' . $var;
592 $value = $this->getVar( $var );
593 if ( !isset( $attribs ) ) {
594 $attribs = [];
595 }
596
597 return $this->parent->getPasswordBox( [
598 'var' => $var,
599 'label' => $label,
600 'attribs' => $attribs,
601 'controlName' => $name,
602 'value' => $value,
603 'help' => $helpData
604 ] );
605 }
606
616 public function getCheckBox( $var, $label, $attribs = [], $helpData = "" ) {
617 $name = $this->getName() . '_' . $var;
618 $value = $this->getVar( $var );
619
620 return $this->parent->getCheckBox( [
621 'var' => $var,
622 'label' => $label,
623 'attribs' => $attribs,
624 'controlName' => $name,
625 'value' => $value,
626 'help' => $helpData
627 ] );
628 }
629
642 public function getRadioSet( $params ) {
643 $params['controlName'] = $this->getName() . '_' . $params['var'];
644 $params['value'] = $this->getVar( $params['var'] );
645
646 return $this->parent->getRadioSet( $params );
647 }
648
656 public function setVarsFromRequest( $varNames ) {
657 return $this->parent->setVarsFromRequest( $varNames, $this->getName() . '_' );
658 }
659
671 public function needsUpgrade() {
672 $status = $this->getConnection();
673 if ( !$status->isOK() ) {
674 return false;
675 }
676
677 try {
678 $this->selectDatabase( $this->db, $this->getVar( 'wgDBname' ) );
679 } catch ( DBConnectionError $e ) {
680 // Don't catch DBConnectionError
681 throw $e;
682 } catch ( DBExpectedError $e ) {
683 return false;
684 }
685
686 return $this->db->tableExists( 'cur', __METHOD__ ) ||
687 $this->db->tableExists( 'revision', __METHOD__ );
688 }
689
695 public function getInstallUserBox() {
696 return Html::openElement( 'fieldset' ) .
697 Html::element( 'legend', [], wfMessage( 'config-db-install-account' )->text() ) .
698 // @phan-suppress-next-line SecurityCheck-DoubleEscaped taint cannot track the helpbox from the rest
699 $this->getTextBox(
700 '_InstallUser',
701 'config-db-username',
702 [ 'dir' => 'ltr' ],
703 $this->parent->getHelpBox( 'config-db-install-username' )
704 ) .
705 // @phan-suppress-next-line SecurityCheck-DoubleEscaped taint cannot track the helpbox from the rest
706 $this->getPasswordBox(
707 '_InstallPassword',
708 'config-db-password',
709 [ 'dir' => 'ltr' ],
710 $this->parent->getHelpBox( 'config-db-install-password' )
711 ) .
712 Html::closeElement( 'fieldset' );
713 }
714
719 public function submitInstallUserBox() {
720 $this->setVarsFromRequest( [ '_InstallUser', '_InstallPassword' ] );
721
722 return Status::newGood();
723 }
724
732 public function getWebUserBox( $noCreateMsg = false ) {
733 $wrapperStyle = $this->getVar( '_SameAccount' ) ? 'display: none' : '';
734 $s = Html::openElement( 'fieldset' ) .
735 Html::element( 'legend', [], wfMessage( 'config-db-web-account' )->text() ) .
736 $this->getCheckBox(
737 '_SameAccount', 'config-db-web-account-same',
738 [ 'class' => 'hideShowRadio', 'rel' => 'dbOtherAccount' ]
739 ) .
740 Html::openElement( 'div', [ 'id' => 'dbOtherAccount', 'style' => $wrapperStyle ] ) .
741 $this->getTextBox( 'wgDBuser', 'config-db-username' ) .
742 $this->getPasswordBox( 'wgDBpassword', 'config-db-password' ) .
743 $this->parent->getHelpBox( 'config-db-web-help' );
744 if ( $noCreateMsg ) {
745 $s .= Html::warningBox( wfMessage( $noCreateMsg )->plain(), 'config-warning-box' );
746 } else {
747 $s .= $this->getCheckBox( '_CreateDBAccount', 'config-db-web-create' );
748 }
749 $s .= Html::closeElement( 'div' ) . Html::closeElement( 'fieldset' );
750
751 return $s;
752 }
753
759 public function submitWebUserBox() {
760 $this->setVarsFromRequest(
761 [ 'wgDBuser', 'wgDBpassword', '_SameAccount', '_CreateDBAccount' ]
762 );
763
764 if ( $this->getVar( '_SameAccount' ) ) {
765 $this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
766 $this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
767 }
768
769 if ( $this->getVar( '_CreateDBAccount' ) && strval( $this->getVar( 'wgDBpassword' ) ) == '' ) {
770 return Status::newFatal( 'config-db-password-empty', $this->getVar( 'wgDBuser' ) );
771 }
772
773 return Status::newGood();
774 }
775
782 public function populateInterwikiTable() {
783 $status = $this->getConnection();
784 if ( !$status->isOK() ) {
785 return $status;
786 }
787 $this->selectDatabase( $this->db, $this->getVar( 'wgDBname' ) );
788
789 if ( $this->db->selectRow( 'interwiki', '1', [], __METHOD__ ) ) {
790 $status->warning( 'config-install-interwiki-exists' );
791
792 return $status;
793 }
794 global $IP;
795 AtEase::suppressWarnings();
796 $rows = file( "$IP/maintenance/interwiki.list",
797 FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES );
798 AtEase::restoreWarnings();
799 $interwikis = [];
800 if ( !$rows ) {
801 return Status::newFatal( 'config-install-interwiki-list' );
802 }
803 foreach ( $rows as $row ) {
804 $row = preg_replace( '/^\s*([^#]*?)\s*(#.*)?$/', '\\1', $row ); // strip comments - whee
805 if ( $row == "" ) {
806 continue;
807 }
808 $row .= "|";
809 $interwikis[] = array_combine(
810 [ 'iw_prefix', 'iw_url', 'iw_local', 'iw_api', 'iw_wikiid' ],
811 explode( '|', $row )
812 );
813 }
814 $this->db->insert( 'interwiki', $interwikis, __METHOD__ );
815
816 return Status::newGood();
817 }
818
819 public function outputHandler( $string ) {
820 return htmlspecialchars( $string );
821 }
822
829 protected function selectDatabase( Database $conn, string $database ) {
830 $schema = $conn->dbSchema();
831 $prefix = $conn->tablePrefix();
832
833 $conn->selectDomain( new DatabaseDomain(
834 $database,
835 // DatabaseDomain uses null for unspecified schemas
836 ( $schema !== '' ) ? $schema : null,
837 $prefix
838 ) );
839
840 return true;
841 }
842}
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:96
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...
selectDatabase(Database $conn, string $database)
Database $db
The database connection.
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.
static meetsMinimumRequirement(IDatabase $conn)
Whether the provided version meets the necessary requirements for this type.
getRadioSet( $params)
Get a set of labelled radio buttons.
MediaWiki exception.
getText()
Format plain text message for the current exception object.
This class is a collection of static functions that serve two purposes:
Definition Html.php:57
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:58
Class for the core installer web interface.
Base class for the more common types of database errors.
Class to handle database/schema/prefix specifications for IDatabase.
Relational database abstraction object.
Definition Database.php:44
tablePrefix( $prefix=null)
Get/set the table prefix.
Definition Database.php:308
dbSchema( $schema=null)
Get/set the db schema.
Definition Database.php:323
selectDomain( $domain)
Set the current domain (database, schema, and table prefix)
Manage a single hardcoded database connection.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:36
getServerVersion()
A string describing the current software version, like from mysql_get_server_info()
getType()
Get the RDBMS type of the server (e.g.
const DBO_DDLMODE
Definition defines.php:16
const DBO_TRX
Definition defines.php:12