MediaWiki REL1_39
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( IDatabase $conn ) {
86 $serverVersion = $conn->getServerVersion();
87 if ( version_compare( $serverVersion, static::$minimumVersion ) < 0 ) {
88 return Status::newFatal(
89 static::$notMinimumVersionMessage, static::$minimumVersion, $serverVersion
90 );
91 }
92
93 return Status::newGood();
94 }
95
99 abstract public function getName();
100
104 abstract public function isCompiled();
105
112 public function checkPrerequisites() {
113 return Status::newGood();
114 }
115
123 abstract public function getConnectForm();
124
134 abstract public function submitConnectForm();
135
144 public function getSettingsForm() {
145 return false;
146 }
147
155 public function submitSettingsForm() {
156 return Status::newGood();
157 }
158
167 abstract public function openConnection();
168
175 abstract public function setupDatabase();
176
188 public function getConnection() {
189 if ( $this->db ) {
190 return Status::newGood( $this->db );
191 }
192
193 $status = $this->openConnection();
194 if ( $status->isOK() ) {
195 $this->db = $status->value;
196 // Enable autocommit
197 $this->db->clearFlag( DBO_TRX );
198 $this->db->commit( __METHOD__ );
199 }
200
201 return $status;
202 }
203
212 private function stepApplySourceFile(
213 $sourceFileMethod,
214 $stepName,
215 $tableThatMustNotExist = false
216 ) {
217 $status = $this->getConnection();
218 if ( !$status->isOK() ) {
219 return $status;
220 }
221 $this->db->selectDB( $this->getVar( 'wgDBname' ) );
222
223 if ( $tableThatMustNotExist && $this->db->tableExists( $tableThatMustNotExist, __METHOD__ ) ) {
224 $status->warning( "config-$stepName-tables-exist" );
225 $this->enableLB();
226
227 return $status;
228 }
229
230 $this->db->setFlag( DBO_DDLMODE );
231 $this->db->begin( __METHOD__ );
232
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 // TODO: Remove special casing in favour of MWExceptionRenderer
429 echo "\nAn error occurred:\n";
430 echo $e->getText();
431 $ret = false;
432 } catch ( Exception $e ) {
433 echo "\nAn error occurred:\n";
434 echo $e->getMessage();
435 $ret = false;
436 }
437 ob_end_flush();
438
439 return $ret;
440 }
441
448 public function preInstall() {
449 }
450
455 public function preUpgrade() {
456 }
457
463 public function getGlobalNames() {
464 return $this->globalNames;
465 }
466
473 public function __construct( $parent ) {
474 $this->parent = $parent;
475 }
476
484 protected static function checkExtension( $name ) {
485 return extension_loaded( $name );
486 }
487
493 public function getReadableName() {
494 // Messages: config-type-mysql, config-type-postgres, config-type-sqlite
495 return wfMessage( 'config-type-' . $this->getName() )->text();
496 }
497
504 public function getGlobalDefaults() {
505 $defaults = [];
506 foreach ( $this->getGlobalNames() as $var ) {
507 if ( isset( $GLOBALS[$var] ) ) {
508 $defaults[$var] = $GLOBALS[$var];
509 }
510 }
511 return $defaults;
512 }
513
518 public function getInternalDefaults() {
520 }
521
528 public function getVar( $var, $default = null ) {
529 $defaults = $this->getGlobalDefaults();
530 $internal = $this->getInternalDefaults();
531 if ( isset( $defaults[$var] ) ) {
532 $default = $defaults[$var];
533 } elseif ( isset( $internal[$var] ) ) {
534 $default = $internal[$var];
535 }
536
537 return $this->parent->getVar( $var, $default );
538 }
539
545 public function setVar( $name, $value ) {
546 $this->parent->setVar( $name, $value );
547 }
548
559 public function getTextBox( $var, $label, $attribs = [], $helpData = "" ) {
560 $name = $this->getName() . '_' . $var;
561 $value = $this->getVar( $var );
562 if ( !isset( $attribs ) ) {
563 $attribs = [];
564 }
565
566 return $this->parent->getTextBox( [
567 'var' => $var,
568 'label' => $label,
569 'attribs' => $attribs,
570 'controlName' => $name,
571 'value' => $value,
572 'help' => $helpData
573 ] );
574 }
575
587 public function getPasswordBox( $var, $label, $attribs = [], $helpData = "" ) {
588 $name = $this->getName() . '_' . $var;
589 $value = $this->getVar( $var );
590 if ( !isset( $attribs ) ) {
591 $attribs = [];
592 }
593
594 return $this->parent->getPasswordBox( [
595 'var' => $var,
596 'label' => $label,
597 'attribs' => $attribs,
598 'controlName' => $name,
599 'value' => $value,
600 'help' => $helpData
601 ] );
602 }
603
613 public function getCheckBox( $var, $label, $attribs = [], $helpData = "" ) {
614 $name = $this->getName() . '_' . $var;
615 $value = $this->getVar( $var );
616
617 return $this->parent->getCheckBox( [
618 'var' => $var,
619 'label' => $label,
620 'attribs' => $attribs,
621 'controlName' => $name,
622 'value' => $value,
623 'help' => $helpData
624 ] );
625 }
626
639 public function getRadioSet( $params ) {
640 $params['controlName'] = $this->getName() . '_' . $params['var'];
641 $params['value'] = $this->getVar( $params['var'] );
642
643 return $this->parent->getRadioSet( $params );
644 }
645
653 public function setVarsFromRequest( $varNames ) {
654 return $this->parent->setVarsFromRequest( $varNames, $this->getName() . '_' );
655 }
656
668 public function needsUpgrade() {
669 $status = $this->getConnection();
670 if ( !$status->isOK() ) {
671 return false;
672 }
673
674 try {
675 $this->db->selectDB( $this->getVar( 'wgDBname' ) );
676 } catch ( DBConnectionError $e ) {
677 // Don't catch DBConnectionError
678 throw $e;
679 } catch ( DBExpectedError $e ) {
680 return false;
681 }
682
683 return $this->db->tableExists( 'cur', __METHOD__ ) ||
684 $this->db->tableExists( 'revision', __METHOD__ );
685 }
686
692 public function getInstallUserBox() {
693 return Html::openElement( 'fieldset' ) .
694 Html::element( 'legend', [], wfMessage( 'config-db-install-account' )->text() ) .
695 // @phan-suppress-next-line SecurityCheck-DoubleEscaped taint cannot track the helpbox from the rest
696 $this->getTextBox(
697 '_InstallUser',
698 'config-db-username',
699 [ 'dir' => 'ltr' ],
700 $this->parent->getHelpBox( 'config-db-install-username' )
701 ) .
702 // @phan-suppress-next-line SecurityCheck-DoubleEscaped taint cannot track the helpbox from the rest
703 $this->getPasswordBox(
704 '_InstallPassword',
705 'config-db-password',
706 [ 'dir' => 'ltr' ],
707 $this->parent->getHelpBox( 'config-db-install-password' )
708 ) .
709 Html::closeElement( 'fieldset' );
710 }
711
716 public function submitInstallUserBox() {
717 $this->setVarsFromRequest( [ '_InstallUser', '_InstallPassword' ] );
718
719 return Status::newGood();
720 }
721
729 public function getWebUserBox( $noCreateMsg = false ) {
730 $wrapperStyle = $this->getVar( '_SameAccount' ) ? 'display: none' : '';
731 $s = Html::openElement( 'fieldset' ) .
732 Html::element( 'legend', [], wfMessage( 'config-db-web-account' )->text() ) .
733 $this->getCheckBox(
734 '_SameAccount', 'config-db-web-account-same',
735 [ 'class' => 'hideShowRadio', 'rel' => 'dbOtherAccount' ]
736 ) .
737 Html::openElement( 'div', [ 'id' => 'dbOtherAccount', 'style' => $wrapperStyle ] ) .
738 $this->getTextBox( 'wgDBuser', 'config-db-username' ) .
739 $this->getPasswordBox( 'wgDBpassword', 'config-db-password' ) .
740 $this->parent->getHelpBox( 'config-db-web-help' );
741 if ( $noCreateMsg ) {
742 $s .= Html::warningBox( wfMessage( $noCreateMsg )->plain(), 'config-warning-box' );
743 } else {
744 $s .= $this->getCheckBox( '_CreateDBAccount', 'config-db-web-create' );
745 }
746 $s .= Html::closeElement( 'div' ) . Html::closeElement( 'fieldset' );
747
748 return $s;
749 }
750
756 public function submitWebUserBox() {
757 $this->setVarsFromRequest(
758 [ 'wgDBuser', 'wgDBpassword', '_SameAccount', '_CreateDBAccount' ]
759 );
760
761 if ( $this->getVar( '_SameAccount' ) ) {
762 $this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
763 $this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
764 }
765
766 if ( $this->getVar( '_CreateDBAccount' ) && strval( $this->getVar( 'wgDBpassword' ) ) == '' ) {
767 return Status::newFatal( 'config-db-password-empty', $this->getVar( 'wgDBuser' ) );
768 }
769
770 return Status::newGood();
771 }
772
779 public function populateInterwikiTable() {
780 $status = $this->getConnection();
781 if ( !$status->isOK() ) {
782 return $status;
783 }
784 $this->db->selectDB( $this->getVar( 'wgDBname' ) );
785
786 if ( $this->db->selectRow( 'interwiki', '1', [], __METHOD__ ) ) {
787 $status->warning( 'config-install-interwiki-exists' );
788
789 return $status;
790 }
791 global $IP;
792 AtEase::suppressWarnings();
793 $rows = file( "$IP/maintenance/interwiki.list",
794 FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES );
795 AtEase::restoreWarnings();
796 $interwikis = [];
797 if ( !$rows ) {
798 return Status::newFatal( 'config-install-interwiki-list' );
799 }
800 foreach ( $rows as $row ) {
801 $row = preg_replace( '/^\s*([^#]*?)\s*(#.*)?$/', '\\1', $row ); // strip comments - whee
802 if ( $row == "" ) {
803 continue;
804 }
805 $row .= "|";
806 $interwikis[] = array_combine(
807 [ 'iw_prefix', 'iw_url', 'iw_local', 'iw_api', 'iw_wikiid' ],
808 explode( '|', $row )
809 );
810 }
811 $this->db->insert( 'interwiki', $interwikis, __METHOD__ );
812
813 return Status::newGood();
814 }
815
816 public function outputHandler( $string ) {
817 return htmlspecialchars( $string );
818 }
819}
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:91
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.
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.
Class for the core installer web interface.
Base class for the more common types of database errors.
Relational database abstraction object.
Definition Database.php:43
Manage a single hardcoded database connection.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:39
getType()
Get the RDBMS type of the server (e.g.
getServerVersion()
A string describing the current software version, like from mysql_get_server_info()
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