Merge new-installer branch to trunk
authorChad Horohoe <demon@users.mediawiki.org>
Fri, 7 May 2010 12:25:01 +0000 (12:25 +0000)
committerChad Horohoe <demon@users.mediawiki.org>
Fri, 7 May 2010 12:25:01 +0000 (12:25 +0000)
* This is not complete yet, and should not be used outside of testing. Using it on a production database may ruin everything. This is the reason for the second entry point of new-index.php. You've been warned.
* Known issues are at [[mw:New-installer_issues]]. Please add new items to the list if you find them.

25 files changed:
config/new-index.php [new file with mode: 0644]
includes/AutoLoader.php
includes/DefaultSettings.php
includes/GlobalFunctions.php
includes/Setup.php
includes/db/LBFactory.php
includes/installer/Installer.i18n.php [new file with mode: 0644]
includes/installer/Installer.php [new file with mode: 0644]
includes/installer/InstallerDBType.php [new file with mode: 0644]
includes/installer/LocalSettingsGenerator.php [new file with mode: 0644]
includes/installer/MysqlInstaller.php [new file with mode: 0644]
includes/installer/OracleInstaller.php [new file with mode: 0644]
includes/installer/PostgresInstaller.php [new file with mode: 0644]
includes/installer/SqliteInstaller.php [new file with mode: 0644]
includes/installer/WebInstaller.php [new file with mode: 0644]
includes/installer/WebInstallerOutput.php [new file with mode: 0644]
maintenance/install.php [new file with mode: 0644]
skins/common/config-cc.css [new file with mode: 0644]
skins/common/config.css [new file with mode: 0644]
skins/common/config.js [new file with mode: 0644]
skins/common/images/critical-32.png [new file with mode: 0644]
skins/common/images/help-22.png [new file with mode: 0644]
skins/common/images/info-32.png [new file with mode: 0644]
skins/common/images/tick-32.png [new file with mode: 0644]
skins/common/images/warning-32.png [new file with mode: 0644]

diff --git a/config/new-index.php b/config/new-index.php
new file mode 100644 (file)
index 0000000..de0a814
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+
+define( 'MW_NO_DB', 1 );
+define( 'MW_NO_SESSION', 1 );
+define( 'MW_CONFIG_CALLBACK', 'wfInstallerConfig' );
+
+function wfInstallerConfig() {
+       // Don't access the database
+       $GLOBALS['wgUseDatabaseMessages'] = false;
+       // Debug-friendly
+       $GLOBALS['wgShowExceptionDetails'] = true;
+       // Don't break forms
+       $GLOBALS['wgExternalLinkTarget'] = '_blank';
+
+       // Extended debugging. Maybe disable before release?
+       $GLOBALS['wgShowSQLErrors'] = true;
+       $GLOBALS['wgShowDBErrorBacktrace'] = true;
+}
+
+chdir( ".." );
+require( './includes/WebStart.php' );
+require_once( './maintenance/updaters.inc' ); // sigh...
+
+// Disable the i18n cache and LoadBalancer
+Language::getLocalisationCache()->disableBackend();
+LBFactory::disableBackend();
+
+// Load the installer's i18n file
+$wgExtensionMessagesFiles['MediawikiInstaller'] = './includes/installer/Installer.i18n.php';
+
+$installer = new WebInstaller( $wgRequest );
+$wgParser->setHook( 'doclink', array( $installer, 'docLink' ) );
+
+if ( !$installer->startSession() ) {
+       $installer->finish();
+       exit;
+}
+
+$session = isset( $_SESSION['installData'] ) ? $_SESSION['installData'] : array();
+
+if ( isset( $session['settings']['_UserLang'] ) ) {
+       $langCode = $session['settings']['_UserLang'];
+} elseif ( !is_null( $wgRequest->getVal( 'UserLang' ) ) ) {
+       $langCode = $wgRequest->getVal( 'UserLang' );
+} else {
+       $langCode = 'en';
+}
+$wgLang = Language::factory( $langCode );
+
+$wgMetaNamespace = $wgCanonicalNamespaceNames[NS_PROJECT];
+
+$session = $installer->execute( $session );
+
+$_SESSION['installData'] = $session;
+
index a217807..4074a26 100644 (file)
@@ -422,6 +422,18 @@ $wgAutoloadLocalClasses = array(
        'RepoGroup' => 'includes/filerepo/RepoGroup.php',
        'UnregisteredLocalFile' => 'includes/filerepo/UnregisteredLocalFile.php',
 
+       # includes/installer
+       'Installer' => 'includes/installer/Installer.php',
+       'InstallerDBType' => 'includes/installer/InstallerDBType.php',
+       'LBFactory_InstallerFake' => 'includes/installer/Installer.php',
+       'LocalSettingsGenerator' => 'includes/installer/LocalSettingsGenerator.php',
+       'WebInstaller' => 'includes/installer/WebInstaller.php',
+       'WebInstallerOutput' => 'includes/installer/WebInstallerOutput.php',
+       'MysqlInstaller' => 'includes/installer/MysqlInstaller.php',
+       'PostgresInstaller' => 'includes/installer/PostgresInstaller.php',
+       'SqliteInstaller' => 'includes/installer/SqliteInstaller.php',
+       'OracleInstaller' => 'includes/installer/OracleInstaller.php',
+
        # includes/media
        'BitmapHandler' => 'includes/media/Bitmap.php',
        'BitmapHandler_ClientOnly' => 'includes/media/Bitmap_ClientOnly.php',
index 8ea4d3d..49b475c 100644 (file)
@@ -625,12 +625,6 @@ $wgDBts2schema      = 'public';
 /** To override default SQLite data directory ($docroot/../data) */
 $wgSQLiteDataDir    = '';
 
-/** Default directory mode for SQLite data directory on creation.
- *  Note that this is different from the default directory mode used
- *  elsewhere.
- */
-$wgSQLiteDataDirMode = 0700;
-
 /**
  * Make all database connections secretly go to localhost. Fool the load balancer
  * thinking there is an arbitrarily large cluster of servers to connect to.
index ef40ef4..31c5f88 100644 (file)
@@ -1815,7 +1815,7 @@ function wfSuppressWarnings( $end = false ) {
                }
        } else {
                if ( !$suppressCount ) {
-                       $originalLevel = error_reporting( E_ALL & ~( E_WARNING | E_NOTICE ) );
+                       $originalLevel = error_reporting( E_ALL & ~( E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE ) );
                }
                ++$suppressCount;
        }
@@ -3326,3 +3326,14 @@ function wfBCP47( $code ) {
        $langCode = implode ( '-' , $codeBCP );
        return $langCode;
 }
+
+function wfArrayMap( $function, $input ) {
+       $ret = array_map( $function, $input );
+       foreach ( $ret as $key => $value ) {
+               $taint = istainted( $input[$key] );
+               if ( $taint ) {
+                       taint( $ret[$key], $taint );
+               }
+       }
+       return $ret;
+}
index 1c7ec74..91964be 100644 (file)
@@ -297,13 +297,15 @@ $wgCookiePrefix = strtr($wgCookiePrefix, "=,; +.\"'\\[", "__________");
 if( !wfIniGetBool( 'session.auto_start' ) )
        session_name( $wgSessionName ? $wgSessionName : $wgCookiePrefix . '_session' );
 
-if( !$wgCommandLineMode && ( $wgRequest->checkSessionCookie() || isset( $_COOKIE[$wgCookiePrefix.'Token'] ) ) ) {
-       wfIncrStats( 'request_with_session' );
-       wfSetupSession();
-       $wgSessionStarted = true;
-} else {
-       wfIncrStats( 'request_without_session' );
-       $wgSessionStarted = false;
+if( !defined( 'MW_NO_SESSION' ) ) {
+       if( !$wgCommandLineMode && ( $wgRequest->checkSessionCookie() || isset( $_COOKIE[$wgCookiePrefix.'Token'] ) ) ) {
+               wfIncrStats( 'request_with_session' );
+               wfSetupSession();
+               $wgSessionStarted = true;
+       } else {
+               wfIncrStats( 'request_without_session' );
+               $wgSessionStarted = false;
+       }
 }
 
 wfProfileOut( $fname.'-SetupSession' );
index 40aca30..f5e889f 100644 (file)
 abstract class LBFactory {
        static $instance;
 
+       /**
+        * Disables all access to the load balancer, will cause all database access
+        * to throw a DBAccessError
+        */
+       public static function disableBackend() {
+               global $wgLBFactoryConf;
+               self::$instance = new LBFactory_Fake( $wgLBFactoryConf );
+       }
+
+       /**
+        * Resets the singleton for use if it's been disabled. Does nothing otherwise
+        */
+       public static function enableBackend() {
+               if( self::$instance instanceof LBFactory_Fake )
+                       self::$instance = null;
+       }
+
        /**
         * Get an LBFactory instance
         */
@@ -197,6 +214,39 @@ class LBFactory_Simple extends LBFactory {
        }
 }
 
+/**
+ * LBFactory class that throws an error on any attempt to use it.
+ * This will typically be done via wfGetDB().
+ * Call LBFactory::disable() to start using this, and LBFactory::enable() to
+ * return to normal behavior
+ */
+class LBFactory_Fake extends LBFactory {
+       function __construct( $conf ) {}
+
+       function newMainLB( $wiki = false) {
+               throw new DBAccessError;
+       }
+       function getMainLB( $wiki = false ) {
+               throw new DBAccessError;
+       }
+       function newExternalLB( $cluster, $wiki = false ) {
+               throw new DBAccessError;
+       }
+       function &getExternalLB( $cluster, $wiki = false ) {
+               throw new DBAccessError;
+       }
+       function forEachLB( $callback, $params = array() ) {}
+}
+
+/**
+ * Exception class for attempted DB access
+ */
+class DBAccessError extends MWException {
+       function __construct() {
+               parent::__construct( "Mediawiki tried to access the database via wfGetDB(). This is not allowed." );
+       }
+}
+
 /**
  * Class for ensuring a consistent ordering of events as seen by the user, despite replication.
  * Kind of like Hawking's [[Chronology Protection Agency]].
diff --git a/includes/installer/Installer.i18n.php b/includes/installer/Installer.i18n.php
new file mode 100644 (file)
index 0000000..c4186f5
--- /dev/null
@@ -0,0 +1,772 @@
+<?php
+/*
+ * Internationalization file for the install/upgrade process. None of the
+ * messages used here are loaded during normal operations, only during
+ * install and upgrade. So you should not put normal messages here.
+ */
+
+$messages = array();
+
+/**
+ * English
+ */
+$messages['en'] = array(
+       'config-title'                    => 'MediaWiki $1 installation',
+       'config-information'              => 'Information',
+       'config-session-error'            => 'Error starting session: $1',
+       'config-session-expired'          => 'Your session data seems to have expired.
+Sessions are configured for a lifetime of $1.
+You can increase this by setting <code>session.gc_maxlifetime</code> in php.ini.
+Restart the installation process.',
+       'config-no-session'               => 'Your session data was lost!
+Check your php.ini and make sure <code>session.save_path</code> is set to an appropriate directory.',
+       'config-session-path-bad'         => 'Your <code>session.save_path</code> (<code>$1</code>) seems to be invalid or unwritable.',
+       'config-show-help'                => 'Help',
+       'config-hide-help'                => 'Hide help',
+       'config-your-language'            => 'Your language:',
+       'config-your-language-help'       => 'Select a language to use during the installation process.',
+       'config-wiki-language'            => 'Wiki language:',
+       'config-wiki-language-help'       => 'Select the language that the wiki will predominantly be written in.',
+       'config-back'                     => '← Back',
+       'config-continue'                 => 'Continue →',
+       'config-page-language'            => 'Language',
+       'config-page-welcome'             => 'Welcome to MediaWiki!',
+       'config-page-dbconnect'           => 'Connect to database',
+       'config-page-upgrade'             => 'Upgrade existing',
+       'config-page-dbsettings'          => 'Database settings',
+       'config-page-name'                => 'Name',
+       'config-page-options'             => 'Options',
+       'config-page-install'             => 'Install',
+       'config-page-complete'            => 'Complete!',
+       'config-page-restart'             => 'Restart installation',
+       'config-page-readme'              => 'Read me',
+       'config-page-releasenotes'        => 'Release notes',
+       'config-page-copying'             => 'Copying',
+       'config-page-upgradedoc'          => 'Upgrading',
+       'config-help-restart'             => 'Do you want to clear all saved data that you have entered and restart the installation process?',
+       'config-restart'                  => 'Yes, restart it',
+       'config-welcome'                  => "=== Environmental checks ===
+We're doing basic checks to see if this environment is suitable for MediaWiki installation. You
+should provide this information if you need help during installation.",
+       'config-copyright'                => "=== Copyright and Terms ===
+
+$1
+
+This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but '''without any warranty'''; without even the implied warranty of '''merchantability''' or '''fitness for a particular purpose'''.
+See the GNU General Public License for more details.
+
+You should have received <doclink href=Copying>a copy of the GNU General Public License</doclink> along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. or [http://www.gnu.org/copyleft/gpl.html read it online].",
+       'config-authors'                  => 'MediaWiki is Copyright © 2001-2010 by Magnus Manske, Brion Vibber, Lee Daniel Crocker, Tim Starling, Erik Möller, Gabriel Wicke, Ævar Arnfjörð Bjarmason, Niklas Laxström, Domas Mituzas, Rob Church, Yuri Astrakhan, Aryeh Gregor, Aaron Schulz, Andrew Garrett, Raimond Spekking, Alexandre Emsenhuber, Siebrand Mazeland, Chad Horohoe and others.',
+       'config-sidebar'                  => "* [http://www.mediawiki.org MediaWiki home]
+* [http://www.mediawiki.org/wiki/Help:Contents User's Guide]
+* [http://www.mediawiki.org/wiki/Manual:Contents Administrator's Guide]
+* [http://www.mediawiki.org/wiki/Manual:FAQ FAQ]",
+       'config-env-good'                 => '<span class="success-message">The environment has been checked.
+You can install MediaWiki.</span>', // FIXME: take span out of message.
+       'config-env-bad'                  => 'The environment has been checked.
+You cannot install MediaWiki.',
+       'config-env-php'                  => 'PHP $1 installed.',
+       'config-env-latest-ok'            => 'You are installing the latest version of Mediawiki.',
+       'config-env-latest-new'           => "'''Note:''' You are installing a development version of Mediawiki.",
+       'config-env-latest-can-not-check' => "'''Note:''' We were unable to retrieve information about the latest MediaWiki release (from [$1]).",
+       'config-env-latest-data-invalid'  => "'''Warning:''' When trying to check if this version was outdated we got invalid data from [$1].",
+       'config-env-latest-old'           => "'''Warning:''' You are installing an outdated version of Mediawiki.",
+       'config-env-latest-help'          => 'You are installing version $1, but the latest version is $2.
+You are advised to use the latest release, which can be downloaded from [http://www.mediawiki.org/wiki/Download mediawiki.org]',
+       'config-no-db'                    => 'Could not find a suitable database driver!',
+       'config-no-db-help'               => 'You need to install a database driver for PHP.
+The following database types are supported: $1.
+
+If you are on shared hosting, ask your hosting provider to install a suitable database driver.
+If you compiled PHP yourself, reconfigure it with a database client enabled, for example using <code>./configure --with-mysql</code>.
+If you installed PHP from a Debian or Ubuntu package, then you also need install the php5-mysql module.',
+       'config-have-db'                  => 'Found database drivers: $1.',
+       'config-register-globals'         => "'''Warning: PHP's <code>[http://php.net/register_globals register_globals]</code> option is enabled.'''
+'''Disable it if you can.'''
+MediaWiki will work, but your server is exposed to potential security vulnerabilities.",
+       'config-magic-quotes-runtime'     => "'''Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] is active!'''
+This option corrupts data input unpredictably.
+You cannot install or use MediaWiki unless this option is disabled.",
+       'config-magic-quotes-sybase'      => "'''Fatal: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] is active!'''
+This option corrupts data input unpredictably.
+You cannot install or use MediaWiki unless this option is disabled.",
+       'config-mbstring'                 => "'''Fatal: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] is active!'''
+This option causes errors and may corrupt data unpredictably.
+You cannot install or use MediaWiki unless this option is disabled.",
+       'config-ze1'                      => "'''Fatal: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] is active!'''
+This option causes horrible bugs with MediaWiki.
+You cannot install or use MediaWiki unless this option is disabled.",
+       'config-safe-mode'                => "'''Warning:''' PHP's [http://www.php.net/features.safe-mode safe mode] is active.
+It may cause problems, particularly if using file uploads and <code>math</code> support.",
+       'config-xml-good'                 => 'Have XML / Latin1-UTF-8 conversion support.',
+       'config-xml-bad'                  => "PHP's XML module is missing.
+MediaWiki requires functions in this module and will not work in this configuration.
+If you're running Mandrake, install the php-xml package.",
+       'config-pcre'                     => 'The PCRE support module appears to be missing.
+MediaWiki requires the Perl-compatible regular expression functions to work.',
+       'config-memory-none'              => 'PHP is configured with no <code>memory_limit</code>',
+       'config-memory-ok'                => "PHP's <code>memory_limit</code> is $1, ok.",
+       'config-memory-raised'            => "PHP's <code>memory_limit</code> is $1, raised to $2.",
+       'config-memory-bad'               => "'''Warning:''' PHP's <code>memory_limit</code> is $1.
+This is probably too low.
+The installation may fail!",
+       'config-xcache'                   => '[http://trac.lighttpd.net/xcache/ XCache] installed',
+       'config-apc'                      => '[http://www.php.net/apc APC] installed',
+       'config-eaccel'                   => '[http://eaccelerator.sourceforge.net/ eAccelerator] installed',
+       'config-no-cache'                 => "'''Warning:''' Could not find [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC] or [http://trac.lighttpd.net/xcache/ XCache].
+Object caching is not enabled.",
+       'config-diff3-good'               => 'Found GNU diff3: <code>$1</code>.',
+       'config-diff3-bad'                => 'GNU diff3 not found.',
+       'config-imagemagick'              => 'Found ImageMagick: <code>$1</code>.
+Image thumbnailing will be enabled if you enable uploads.',
+       'config-gd'                       => 'Found GD graphics library built-in.
+Image thumbnailing will be enabled if you enable uploads.',
+       'config-no-scaling'               => 'Could not find GD library or ImageMagick.
+Image thumbnailing will be disabled.',
+       'config-dir'                      => 'Installation directory: <code>$1</code>.',
+       'config-uri'                      => 'Script URI path: <code>$1</code>.',
+       'config-no-uri'                   => "'''Error:''' Could not determine the current URI.
+Installation aborted.",
+       'config-dir-not-writable-group'   => "'''Error:''' Cannot write config file.
+Installation aborted.
+
+We've determined the user your webserver is running as. Make the
+<code><nowiki>config</nowiki></code> directory writable by it to continue. On a Unix/Linux system:
+
+<pre>cd $1
+chgrp $2 config
+chmod g+w config</pre>",
+       'config-dir-not-writable-nogroup'   => "'''Error:''' Cannot write config file.
+Installation aborted.
+
+We couldn't determine the user your webserver is running as. Make the
+<code><nowiki>config</nowiki></code> directory globally writable by it (and others!) to continue. On
+a Unix/Linux system do:
+
+<pre>cd $1
+chmod a+w config</pre>",
+       'config-file-extension'           => 'Installing MediaWiki with <code>$1</code> file extensions.',
+       'config-shell-locale'             => 'Detected shell locale "$1"',
+       'config-uploads-safe'             => 'Default uploads directory is safe from arbitrary scripts execution.',
+       'config-uploads-not-safe'         => "'''Warning:''' Your default uploads directory <code>$1</code> is vulnerable to arbitrary scripts execution.
+Although MediaWiki checks all uploaded files for security threats, it is highly recommended to [http://www.mediawiki.org/wiki/Manual:Security#Upload_security close this hole] before enabling uploads.",
+       'config-db-type'                  => 'Database type:',
+       'config-db-host'                  => 'Database host:',
+       'config-db-host-help'             => 'If your database server is on different server, enter the host name or IP address here.
+
+If you are using shared web hosting, your hosting provider should give you the correct host name in their documentation.',
+       'config-db-wiki-settings'         => 'Identify this wiki',
+       'config-db-name'                  => 'Database name:',
+       'config-db-name-help'             => 'Choose a name that identifies your wiki.
+It should not contain spaces or hyphens.
+
+If you are using shared web hosting, your hosting provider will either give you a specific database name to use, or let you create databases via a control panel.',
+       'config-db-install-account'       => 'User account for installation',
+       'config-db-username'              => 'Database username:',
+       'config-db-password'              => 'Database password:',
+       'config-db-install-help'          => 'Enter the username and password that will be used to connect to the database during the installation process.',
+       'config-db-account-lock'          => 'Use the same username and password during normal operation',
+       'config-db-wiki-account'          => 'User account for normal operation',
+       'config-db-wiki-help'             => 'Enter the username and password that will be used to connect to the database during normal wiki operation.
+If the account does not exist, and the installation account has sufficient privileges, this user account will be created with the minimum privileges required to operate the wiki.',
+       'config-db-prefix'                => 'Database table prefix:',
+       'config-db-prefix-help'           => 'If you need to share one database between multiple wikis, or between MediaWiki and another web application, you may choose to add a prefix to all the table names to avoid conflicts.
+Do not use spaces or hyphens.
+
+This field is usually left empty.',
+       'config-db-charset'               => 'Database character set',
+       'config-charset-mysql5-binary'    => 'MySQL 4.1/5.0 binary',
+       'config-charset-mysql5'           => 'MySQL 4.1/5.0 UTF-8',
+       'config-charset-mysql4'           => 'MySQL 4.0 backwards-compatible UTF-8',
+       'config-charset-help'             => "'''WARNING:''' If you use '''backwards-compatible UTF-8''' on MySQL 4.1+, and subsequently back up the database with <code>mysqldump</code>, it may destroy all non-ASCII characters, irreversibly corrupting your backups!.
+
+In '''binary mode''', MediaWiki stores UTF-8 text to the database in binary fields.
+This is more efficient than MySQL's UTF-8 mode, and allows you to use the full range of Unicode characters.
+In '''UTF-8 mode''', MySQL will know what character set your data is in, and can present and convert it appropriately,
+but it will not let you store characters above the [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+       'config-mysql-old'                => 'MySQL $1 or later is required, you have $2.',
+       'config-db-port'                  => 'Database port:',
+       'config-db-schema'                => 'Schema for MediaWiki',
+       'config-db-ts2-schema'            => 'Schema for tsearch2',
+       'config-db-schema-help'           => 'The above schemas are usually correct.
+Only change them if you know you need to.',
+       'config-sqlite-dir'               => 'SQLite data directory:',
+       'config-sqlite-dir-help'          => "SQLite stores all data in a single file.
+
+The directory you provide must be writable by the webserver during installation.
+
+It should '''not''' be accessible via the web, this is why we're not putting it where your PHP files
+are.
+
+We'll write out a <code>.htaccess</code> file along with it, but if that fails someone can gain
+access to your raw database. That includes raw user data (E-Mails, hashed passwords) as well as
+deleted revisions and other restricted data on the wiki.
+
+Consider putting the database somewhere altogether, for example <code>/var/lib/mediawiki/yourwiki</code>.",
+       'config-type-mysql'               => 'MySQL',
+       'config-type-postgres'            => 'PostgreSQL',
+       'config-type-sqlite'              => 'SQLite',
+       'config-type-oracle'              => 'Oracle',
+       'config-header-mysql'             => 'MySQL settings',
+       'config-header-postgres'          => 'PostgreSQL settings',
+       'config-header-sqlite'            => 'SQLite settings',
+       'config-header-oracle'            => 'Oracle settings',
+       'config-invalid-db-type'          => 'Invalid database type',
+       'config-missing-db-name'          => 'You must enter a value for "Database name"',
+       'config-invalid-db-name'          => 'Invalid database name "$1".
+It may only contain numbers, letters and underscores.',
+       'config-invalid-db-prefix'        => 'Invalid database prefix "$1".
+It may only contain numbers, letters and underscores.',
+       'config-connection-error'         => '$1.
+
+Check the host, username and password below and try again.',
+       'config-invalid-schema'           => 'Invalid schema for MediaWiki "$1".
+Use only letters, numbers and underscores.',
+       'config-invalid-ts2schema'        => 'Invalid schema for tsearch2 "$1".
+Use only letters, numbers and underscores.',
+       'config-postgres-old'             => 'PostgreSQL $1 or later is required, you have $2.',
+       'config-sqlite-name-help'         => 'Choose a name that identifies your wiki.
+Do not use spaces or hyphens.
+This will be used for the SQLite data file name.',
+       'config-sqlite-parent-unwritable-group' => 'Cannot create the data directory <code><nowiki>$1</nowiki></code>, because the parent directory <code><nowiki>$2</nowiki></code> is not writable by the webserver.
+
+We\'ve determined the user your webserver is running as. Make the <code><nowiki>$3</nowiki></code>
+directory writable by it to continue. On a Unix/Linux system do:
+
+<pre>cd $2
+mkdir $3
+chgrp $4 $3
+chmod g+w $3</pre>',
+       'config-sqlite-parent-unwritable-nogroup' => 'Cannot create the data directory <code><nowiki>$1</nowiki></code>, because the parent directory <code><nowiki>$2</nowiki></code> is not writable by the webserver.
+
+We couldn\'t determine the user your webserver is running as. Make the <code><nowiki>$3</nowiki></code>
+directory globally writable by it (and others!) to continue. On a Unix/Linux system do:
+
+<pre>cd $2
+mkdir $3
+chmod a+w $3</pre>',
+       'config-sqlite-mkdir-error'       => 'Error creating the data directory "$1".
+Check the location and try again.',
+       'config-sqlite-dir-unwritable'    => 'Unable to write to the directory "$1".
+Change its permissions so that the webserver can write to it, and try again.',
+       'config-sqlite-connection-error'  => '$1.
+
+Check the data directory and database name below and try again.',
+       'config-sqlite-readonly'          => 'File <code>$1</code> is not writeable.',
+       'config-sqlite-cant-create-db'    => 'Could not create database file <code>$1</code>.',
+       'config-can-upgrade'              => "There are MediaWiki tables in this database.
+To upgrade them to MediaWiki $1, click '''Continue'''.",
+       'config-upgrade-done'             => "Upgrade complete.
+
+You can now [$1 start using your wiki].
+
+If you want to regenerate your <code>LocalSettings.php</code> file, click the button below.
+This is '''not recommended''' unless you are having problems with your wiki.",
+       'config-regenerate'               => 'Regenerate LocalSettings.php →',
+       'config-show-table-status'        => 'SHOW TABLE STATUS query failed!',
+       'config-unknown-collation'        => "'''Warning:''' Datbase is using unrecognised collation.",
+       'config-db-web-account'           => 'Database account for web access',
+       'config-db-web-help'              => 'Select the username and password that the web server will use to connect to the database server, during ordinary operation of the wiki.',
+       'config-db-web-account-same'      => 'Use the same account as for installation',
+       'config-db-web-create'            => 'Create the account if it does not already exist',
+       'config-db-web-no-create-privs'   => 'The account you specified for installation does not have enough privileges to create an account.
+The account you specify here must already exist.',
+       'config-mysql-engine'             => 'Storage engine:',
+       'config-mysql-innodb'             => 'InnoDB',
+       'config-mysql-myisam'             => 'MyISAM',
+       'config-mysql-engine-help'        => "'''InnoDB''' is almost always the best option, since it has good concurrency support.
+
+'''MyISAM''' may be faster in single-user or read-only installations.
+MyISAM databases tend to get corrupted more often than InnoDB databases.",
+       'config-mysql-charset'            => 'Database character set:',
+       'config-mysql-binary'             => 'Binary',
+       'config-mysql-utf8'               => 'UTF-8',
+       'config-mysql-charset-help'       => "In '''binary mode''', MediaWiki stores UTF-8 text to the database in binary fields.
+This is more efficient than MySQL's UTF-8 mode, and allows you to use the full range of Unicode characters.
+
+In '''UTF-8 mode''', MySQL will know what character set your data is in, and can present and convert it appropriately, but it will not let you store characters above the [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+       'config-site-name'                => 'Name of wiki:',
+       'config-site-name-help'           => "This will appear in the browser's title bar and various other places.",
+       'config-site-name-blank'          => 'Enter a site name.',
+       'config-project-namespace'        => 'Project namespace:',
+       'config-ns-generic'               => 'Project',
+       'config-ns-site-name'             => 'Same as the wiki name: $1',
+       'config-ns-other'                 => 'Other (specify)',
+       'config-ns-other-default'         => 'MyWiki',
+       'config-project-namespace-help'   => 'Following Wikipedia\'s example, many wikis keep their policy pages separate from their content pages, in a "\'\'\'project namespace\'\'\'".
+All page titles in this namespace start with a certain prefix, which you can specify here.
+Traditionally, this prefix is derived from the name of the wiki, but it cannot contain punctuation characters such as "#" or ":".',
+       'config-ns-invalid'               => 'The specified namespace "<nowiki>$1</nowiki>" is invalid.
+Specify a different project namespace',
+       'config-admin-default-username'   => 'WikiSysop',
+       'config-admin-box'                => 'Administrator account',
+       'config-admin-name'               => 'Your name:',
+       'config-admin-password'           => 'Password:',
+       'config-admin-password-confirm'   => 'Password again:',
+       'config-admin-help'               => 'Enter your preferred username here, for example "Joe Bloggs".
+This is the name you will use to log in to the wiki.',
+       'config-admin-name-blank'         => 'Enter an administrator username.',
+       'config-admin-name-invalid'       => 'The specified username "<nowiki>$1</nowiki>" is invalid.
+Specify a different username.',
+       'config-admin-password-blank'     => 'Enter a password for the administrator account.',
+       'config-admin-password-same'      => 'The password must not be the same as the username.',
+       'config-admin-password-mismatch'  => 'The two passwords you entered do not match.',
+       'config-admin-email'              => 'E-mail address:',
+       'config-admin-email-help'         => 'Enter an e-mail address here to allow you to receive e-mail from other users on the wiki, reset your password, and be notified of changes to pages on your watchlist.',
+       'config-admin-error-user'         => 'Internal error when creating an admin with the name "<nowiki>$1</nowiki>".',
+       'config-admin-error-password'     => 'Internal error when setting a password for the admin "<nowiki>$1</nowiki>": <pre>$2</pre>',
+       'config-subscribe'                => 'Subscribe to the [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce release announcements mailing list].',
+       'config-subscribe-help'           => 'This is a low-volume mailing list used for release announcements, including important security announcements.
+You should subscribe to it and update your MediaWiki installation when new versions come out.',
+       'config-almost-done'              => 'You are almost done!
+You can now skip the remaining configuration and install the wiki right now.',
+       'config-optional-continue'        => 'Ask me more questions.',
+       'config-optional-skip'            => "I'm bored already, just install the wiki.",
+       'config-profile'                  => 'User rights profile:',
+       'config-profile-wiki'             => 'Traditional wiki',
+       'config-profile-no-anon'          => 'Account creation required',
+       'config-profile-fishbowl'         => 'Authorized editors only',
+       'config-profile-private'          => 'Private wiki',
+       'config-profile-help'             => "Wikis work best when you let as many people edit them as possible.
+In MediaWiki, it is easy to review the recent changes, and to revert any damage that is done by naive or malicious users.
+
+However, many have found MediaWiki to be useful in a wide variety of roles, and sometimes it is not easy to convince everyone of the benefits of the wiki way.
+So you have the the choice.
+
+A '''traditional wiki''' allows anyone to edit, without even logging in.
+A wiki with '''account creation required''' provides extra accountability, but may deter casual contributors.
+
+The '''authorized editors only''' scenario allows approved users to edit, but the public can view the pages, including history.
+A '''private wiki''' only allows approved users to view pages, with the same group allowed to edit.
+
+More complex user rights configurations are available after installation, see the [http://www.mediawiki.org/wiki/Manual:User_rights relevant manual entry].",
+       'config-license'                  => 'Copyright and license:',
+       'config-license-none'             => 'No license footer',
+       'config-license-gfdl-old'         => 'GNU Free Documentation License 1.2 or later',
+       'config-license-gfdl-current'     => 'GNU Free Documentation License 1.3 or later',
+       'config-license-pd'               => 'Public Domain',
+       'config-license-cc-choose'        => 'A Creative Commons license',
+       'config-license-help'             => "Many public wikis put all contributions under a [http://freedomdefined.org/Definition free license].
+This helps to create a sense of community ownership and encourages long-term contribution.
+It is not generally necessary for a private or corporate wiki.
+
+If you want to be able to use text from Wikipedia, and you want Wikipedia to be able to accept text copied from your wiki, you should choose '''GNU Free Documentation License 1.2'''.
+However, this license has some features which make reuse and interpretation difficult.
+
+If Wikipedia-compatibility is not important, '''Creative Commons''' with the '''Share Alike''' option (cc-by-sa) is a good choice.",
+       'config-email-settings'           => 'E-mail settings',
+       'config-enable-email'             => 'Enable outbound e-mail',
+       'config-enable-email-help'        => "If you want e-mail to work, [http://www.php.net/manual/en/mail.configuration.php PHP's mail settings] need to be configured correctly.
+If you do not want any e-mail features, you can disable them here.",
+       'config-email-user'               => 'Enable user-to-user e-mail',
+       'config-email-user-help'          => 'All users to send each other e-mail, if they have enabled it in their preferences',
+       'config-email-usertalk'           => 'Enable user talk page notification',
+       'config-email-usertalk-help'      => 'Allow users to receive notifications on user talk page changes, if they have enabled it in their preferences',
+       'config-email-watchlist'          => 'Enable watchlist notification',
+       'config-email-watchlist-help'     => 'Allow users to receive notifications to their watched pages, if they have enabled it in their preferences',
+       'config-email-auth'               => 'Enable e-mail authentication',
+       'config-email-auth-help'          => "If this option is enabled, users have to confirm their e-mail address using a link sent to them whenever they set or change it.
+Only authenticated e-mail addresses can receive e-mails from other users or change notification e-mails.
+Setting this option is '''recommended''' for public wikis because of potential abuse of the e-mail features.",
+       'config-email-sender'             => 'Return e-mail address:',
+       'config-email-sender-help'        => 'Enter the e-mail address to use as the return address on outbound e-mail.
+This is where bounces will be sent.
+Many mail servers require at least the domain name part to be valid.',
+       'config-upload-settings'          => 'Images and file uploads',
+       'config-upload-enable'            => 'Enable file uploads',
+       'config-upload-help'              => "File uploads potentially expose your server to security risks.
+For more information, read the [http://www.mediawiki.org/wiki/Manual:Security security section] in the manual.
+
+To enable file uploads, change the mode on the <code>images</code> subdirectory under MediaWiki's root directory so that the web server can write to it.
+Then enable this option.",
+       'config-upload-deleted'           => 'Directory for deleted files:',
+       'config-upload-deleted-help'      => 'Choose a directory in which to archive deleted files.
+Ideally, this should not be accessible from the web.',
+       'config-logo'                     => 'Logo URL:',
+       'config-logo-help'                => "MediaWiki's default skin includes space for a 135x135 pixel logo in the top left corner.
+Upload an image of the appropriate size, and enter the URL here.
+
+If you do not want a logo, leave this box blank.",
+       'config-cc-error'                 => 'The Creative Commons license chooser gave no result.
+Enter the license name manually.',
+       'config-cc-again'                 => 'Pick again...',
+       'config-cc-not-chosen'            => 'Choose which Creative Commons license you want and click "proceed".',
+       'config-advanced-settings'        => 'Advanced configuration',
+       'config-cache-options'            => 'Settings for object caching:',
+       'config-cache-help'               => 'Object caching is used to improve the speed of MediaWiki by caching frequently used data.
+Medium to large sites are highly encouraged to enable this, and small sites will see benefits as well.',
+       'config-cache-none'               => 'No caching.
+No functionality is removed, but speed may be impacted.',
+       'config-cache-accel'              => 'PHP object caching (APC, eAccelerator or XCache)',
+       'config-cache-memcached'          => 'Use Memcached (requires additional setup and configuration)',
+       'config-cache-db'                 => 'Cache data into the database',
+       'config-cache-anything'           => 'MediaWiki will attempt to cache data anywhere possible, except in Memcached, unless indicated explicitely',
+       'config-memcached-servers'        => 'Memcached servers:',
+       'config-memcached-help'           => 'List of IP addresses to use for Memcached.
+Should be separated with commas and specify the port to be used (for example: 1.2.3.4:56, 7.8.9.10:11).',
+       'config-extensions'               => 'Extensions',
+       'config-extensions-help'          => 'The extensions listed above were detected in your <code>./extensions</code> directory.
+
+They may require additional configuration, but you can enable them now',
+       'config-install-step-done'        => 'Done',
+       'config-install-step-failed'      => 'Failed',
+       'config-install-extensions'       => 'Including extensions',
+       'config-install-database'         => 'Setting up database',
+       'config-install-pg-schema-failed' => 'Tables creation failed.
+Make sure that the user "$1" can write to the schema "$2".',
+       'config-install-tables'           => 'Creating tables',
+       'config-install-interwiki-sql'    => 'Could not find file <code>interwiki.sql</code>',
+       'config-install-secretkey'        => 'Generating secret key',
+       'config-insecure-secretkey'       => "'''Warning:''' Unable to create secure <code>\$wgSecretKey</code>.
+Consider changing it manually.",
+       'config-install-sysop'            => 'Creating administrator user account',
+       'config-install-localsettings'    => 'Creating <code>LocalSettings.php</code>',
+       'config-install-localsettings-unwritable' => 'Warning: Could not write <code>LocalSettings.php</code>.
+Create it yourself, using the following text:',
+       'config-install-done'             => "'''Congratulations!'''
+You have successfully installed MediaWiki.
+
+We've generated a <code>LocalSettings.php</code> file for you. It contains all your configuration.
+
+You will need to move it from <code>./config/LocalSettings.php</code> to <code>./LocalSettings.php</code> in order for MediaWiki to work:
+
+On a Unix/Linux system:
+
+<pre>
+mv ./config/LocalSettings.php ./LocalSettings.php
+</pre>
+
+When that's done, you can [$1 '''enter your wiki''']",
+       'config-install-done-moved'       => "'''Congratulations!'''
+You have successfully installed MediaWiki.
+
+[$1 Enter your wiki]",
+);
+
+/** Dutch (Nederlands)
+ * @author Siebrand
+ */
+$messages['nl'] = array(
+       'config-title' => 'Installatie MediaWiki $1',
+       'config-information' => 'Informatie',
+       'config-session-error' => 'Fout bij het begin van de sessie: $1',
+       'config-session-expired' => 'Uw sessiegegevens zijn verlopen.
+Sessies zijn ingesteld om een levensduur van $1 te hebben.
+U kunt deze wijzigen via de instelling <code>session.gc_maxlifetime</code> in php.ini.
+Begin het installatieproces opnieuw.',
+       'config-no-session' => 'Uw sessiegegevens zijn verloren gegaan.
+Controleer uw php.ini en zorg dat er een juiste map is ingesteld voor <code>session.save_path</code>.',
+       'config-session-path-bad' => 'Uw <code>session.save_path</code> (<code>$1</code>) lijkt onjuist of er kan niet in geschreven worden.',
+       'config-show-help' => 'Hulp',
+       'config-hide-help' => 'Hulp verbergen',
+       'config-your-language' => 'Uw taal:',
+       'config-your-language-help' => 'Selecteer een taal om tijdens het installatieproces te gebruiken.',
+       'config-wiki-language' => 'Wikitaal:',
+       'config-wiki-language-help' => 'Selecteer de taal waar de wiki voornamelijk in wordt geschreven.',
+       'config-back' => '← Terug',
+       'config-continue' => 'Doorgaan →',
+       'config-page-language' => 'Taal',
+       'config-page-welcome' => 'Welkom bij MediaWiki!',
+       'config-page-dbconnect' => 'Verbinding maken met database',
+       'config-page-upgrade' => 'Bestaande bijwerken',
+       'config-page-dbsettings' => 'Databaseinstellingen',
+       'config-page-name' => 'Naam',
+       'config-page-options' => 'Opties',
+       'config-page-install' => 'Installeren',
+       'config-page-complete' => 'Afgerond!',
+       'config-page-restart' => 'Installatie herstarten',
+       'config-page-readme' => 'Lees mij',
+       'config-page-releasenotes' => 'Release notes',
+       'config-page-copying' => 'Kopiëren',
+       'config-page-upgradedoc' => 'Bijwerken',
+       'config-help-restart' => 'Wilt u alle opgeslagen gegevens die u hebt ingevoerd wissen en het installatieproces opnieuw starten?',
+       'config-restart' => 'Ja, opnieuw starten',
+       'config-authors' => 'MediaWiki is Copyright © 2001-2010 door Magnus Manske, Brion Vibber, Lee Daniel Crocker, Tim Starling, Erik Möller, Gabriel Wicke, Ævar Arnfjörð Bjarmason, Niklas Laxström, Domas Mituzas, Rob Church, Yuri Astrakhan, Aryeh Gregor, Aaron Schulz, Andrew Garrett, Raimond Spekking, Alexandre Emsenhuber, Siebrand Mazeland, Chad Horohoe en anderen.',
+       'config-env-good' => 'De omgeving is gecontroleerd.
+U kunt MediaWiki installeren.',
+       'config-env-bad' => 'De omgeving is gecontroleerd.
+U kunt MediaWiki niet installeren.',
+       'config-env-php' => 'PHP $1 is geïnstalleerd.',
+       'config-env-latest-ok' => 'U bent bezig de meest recente versie van MediaWiki te installeren.',
+       'config-env-latest-new' => "'''Let op:''' U bent bezig een ontwikkelversie van MediaWiki te installeren.",
+       'config-env-latest-old' => "'''Waarschuwing:''' U bent bezig een verouderde versie van MediaWiki te installeren.",
+       'config-env-latest-help' => 'U bent bezig versie $1 te installeren, maar de meest recente versie is $2.
+U wordt aangeraden de meest recente versie te gebruiken die u kunt downloaden van [http://www.mediawiki.org/wiki/Download mediawiki.org].',
+       'config-no-db' => 'Er kon geen geschikte databasedriver geladen worden!',
+       'config-no-db-help' => 'U moet een databasedriver installeren voor PHP.
+De volgende databases worden ondersteund: $1.
+
+Als u op een gedeelde omgeving zit, vraag dan uw hostingprovider een geschikte databasedriver te installeren.
+Als u PHP zelf hebt gecompileerd, wijzig dan uw instellingen zodat een databasedriver wordt geactiveerd, bijvoorbeeld via <code>./configure --with-mysql</code>.',
+       'config-have-db' => 'Gevonden databasedrivers: $1.',
+       'config-register-globals' => "'''Waarschuwing: De PHP-optie <code>[http://php.net/register_globals register_globals]</code> is ingeschakeld.'''
+'''Schakel deze uit als dat mogelijk is.'''
+MediaWiki kan ermee werken, maar uw server is dan meer kwetsbaar voor beveiligingslekken.",
+       'config-magic-quotes-runtime' => "'''Onherstelbare fout: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] is actief!'''
+Deze instelling zorgt voor gegevenscorruptie.
+U kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
+       'config-magic-quotes-sybase' => "'''Onherstelbare fout: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_sybase] is actief!'''
+Deze instelling zorgt voor gegevenscorruptie.
+U kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
+       'config-mbstring' => "'''Onherstelbare fout: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] is actief!'''
+Deze instelling zorgt voor gegevenscorruptie.
+U kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
+       'config-ze1' => "'''Onherstelbare fout: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] is actief!'''
+Deze instelling zorgt voor grote problemen in MediaWiki.
+U kunt MediaWiki niet installeren tenzij deze instelling is uitgeschakeld.",
+       'config-safe-mode' => "'''Waarschuwing:'''
+'''PHP's [http://www.php.net/features.safe-mode veilige modus] actief is.'''
+Dit kan problemen veroorzaken, vooral bij het uploaden van bestanden en ondersteuning van <code>math</code>.",
+       'config-xml-bad' => 'De XML-module van PHP ontbreekt.
+MediaWiki heeft de functies van deze module nodig en werkt niet zonder deze module.',
+       'config-pcre' => 'De ondersteuningsmodule PCRE lijkt te missen.
+MediaWiki vereist dat de met Perl compatibele reguliere expressies werken.',
+       'config-memory-none' => 'PHP is ingesteld zonder <code>memory_limit</code>',
+       'config-memory-ok' => "PHP's <code>memory_limit</code> is $1. In orde.",
+       'config-memory-raised' => "PHP's <code>memory_limit</code> is $1. Verhoogd tot $2.",
+       'config-memory-bad' => "'''Waarschuwing:''' PHP's <code>memory_limit</code> is $1.
+Dit is waarschijnlijk te laag.
+De installatie kan mislukken!",
+       'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] geïnstalleerd',
+       'config-apc' => '[http://www.php.net/apc APC] geïnstalleerd',
+       'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] geïnstalleerd',
+       'config-no-cache' => "'''Waarschuwing:''' [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC] of [http://trac.lighttpd.net/ xcache / XCache] is niet aangetroffen.
+Het cachen van objecten is niet ingeschakeld.",
+       'config-diff3-good' => 'GNU diff3 aangetroffen: <code>$1</code>.',
+       'config-diff3-bad' => 'GNU diff3 niet aangetroffen.',
+       'config-imagemagick' => 'ImageMagick aangetroffen: <code>$1</code>.
+Het aanmaken van miniaturen van afbeeldingen wordt ingeschakeld als u uploaden inschakelt.',
+       'config-gd' => 'Ingebouwde GD grafische bibliotheek aangetroffen.
+Het aanmaken van miniaturen van afbeeldingen wordt ingeschakeld als u uploaden inschakelt.',
+       'config-no-scaling' => 'De GD-bibliotheek en ImageMagick zijn niet aangetroffen.
+Het maken van miniaturen van afbeeldingen wordt uitgeschakeld.',
+       'config-dir' => 'Installatiemap: <code>$1</code>.',
+       'config-uri' => 'Script URI-pad: <code>$1</code>.',
+       'config-no-uri' => "'''Fout:''' de huidige URI kon niet vastgesteld worden.
+De installatie is afgebroken.",
+       'config-file-extension' => 'MediaWiki wordt geinstalleerd met <code>$1</code> als bestandsextensie.',
+       'config-shell-locale' => 'Als shelllocale is "$1" herkend',
+       'config-uploads-safe' => 'De uploadmap is beveiligd tegen het arbitrair uitvoeren van scripts.',
+       'config-uploads-not-safe' => "'''Waarschuwing:''' uw uploadmap <code>$1</code> kan gebruikt worden voor het arbitrair uitvoeren van scripts.
+Hoewel MediaWiki alle toegevoegde bestanden  controleert op bedreigingen, is het zeer aan te bevelen het [http://www.mediawiki.org/wiki/Manual:Security#Upload_security beveiligingslek te verhelpen] alvorens uploads in te schakelen.",
+       'config-db-type' => 'Databasetype:',
+       'config-db-host' => 'Databasehost:',
+       'config-db-host-help' => 'Als uw databaseserver een andere server is, voer dan de hostnaam of het IP-adres hier in.
+Als u gebruik maakt van gedeelde webhosting, hoort uw provider u de juiste hostnaam te hebben verstrekt.',
+       'config-db-wiki-settings' => 'Identificeer deze wiki',
+       'config-db-name' => 'Databasenaam:',
+       'config-db-name-help' => 'Kies een naam die uw wiki identificeert.
+Er mogen geen spaties of koppeltekens gebruikt worden.
+Als u gebruik maakt van gedeelde webhosting, dan hoort uw provider ofwel u een te gebruiken databasenaam gegeven te hebben, of u aangegeven te hebben hoe u databases kunt aanmaken.',
+       'config-db-install-account' => 'Gebruiker voor installatie',
+       'config-db-username' => 'Gebruikersnaam voor database:',
+       'config-db-password' => 'Wachtwoord voor database:',
+       'config-db-install-help' => 'Voer de gebruikersnaam en het wachtwoord in die worden gebruikt voor de databaseverbinding tijdens het installatieproces.',
+       'config-db-account-lock' => 'Dezelfde gebruiker en wachwoord gebruiken na de installatie',
+       'config-db-wiki-account' => 'Gebruiker voor na de installatie',
+       'config-db-wiki-help' => 'Selecteer de gebruikersnaam en het wachtwoord die gebruikt worden om verbinding te maken met de database na de installatie.
+Als de gebruiker niet bestaat en de gebruiker die tijdens de installatie gebruikt wordt voldoende rechten heeft, wordt deze gebruiker aangemaakt met de minimaal benodigde rechten voor het laten werken van de wiki.',
+       'config-db-prefix' => 'Databasetabelvoorvoegsel:',
+       'config-db-prefix-help' => "Als u een database moet gebruiken voor meerdere wiki's, of voor MediaWiki en een andere applicatie, dan kunt u ervoor kiezen om een voorvoegsel toe te voegen aan de tabelnamen om conflicten te voorkomen.
+Gebruik geen spaties of koppeltekens.
+
+Dit veld wordt meestal leeg gelaten.",
+       'config-db-charset' => 'Tekenset voor de database',
+       'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binair',
+       'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+       'config-charset-mysql4' => 'MySQL 4.0 UTF-8-compatibel',
+       'config-mysql-old' => 'U moet MySQL $1 of later gebruiken.
+U gebruikt $2.',
+       'config-db-port' => 'Databasepoort:',
+       'config-db-schema' => 'Schema voor MediaWiki',
+       'config-db-ts2-schema' => 'Schema voor tsearch2',
+       'config-db-schema-help' => "De bovenstaande schema's kloppen meestal.
+Wijzig ze alleen als u weet dat u ze nodig hebt.",
+       'config-sqlite-dir' => 'Gegevensmap voor SQLite:',
+       'config-type-mysql' => 'MySQL',
+       'config-type-postgres' => 'PostgreSQL',
+       'config-type-sqlite' => 'SQLite',
+       'config-type-oracle' => 'Oracle',
+       'config-header-mysql' => 'MySQL-instellingen',
+       'config-header-postgres' => 'PostgreSQL-instellingen',
+       'config-header-sqlite' => 'SQLite-instellingen',
+       'config-header-oracle' => 'Oracle-instellingen',
+       'config-invalid-db-type' => 'Ongeldig databasetype',
+       'config-missing-db-name' => 'U moet een waarde ingeven voor "Databasenaam"',
+       'config-invalid-db-name' => 'Ongeldige database naam "$1".
+Deze mag alleen cijfers, letters en liggende streepjes bevatten.',
+       'config-invalid-db-prefix' => 'Ongeldig databasevoorvoegsel "$1".
+Dit mag alleen cijfers, letters en liggende streepjes bevatten.',
+       'config-connection-error' => '$1.
+
+Controleer de host, gebruikersnaam en wachtwoord hieronder in en probeer het opnieuw.',
+       'config-invalid-schema' => 'Ongeldige schema voor MediaWiki "$1".
+Gebruik alleen letters, cijfers en liggende streepjes.',
+       'config-invalid-ts2schema' => 'Ongeldig schema voor tsearch "$1".
+Gebruik alleen letters, cijfers en liggende streepjes.',
+       'config-postgres-old' => 'PostgreSQL $1 of hoger is vereist.
+U gebruikt $2.',
+       'config-sqlite-name-help' => 'Kies een naam die uw wiki identificeert.
+Gebruik geen spaties of koppeltekens.
+Deze naam wordt gebruikt voor het gegevensbestands van SQLite.',
+       'config-sqlite-mkdir-error' => 'Er is een fout opgetreden bij het aanmaken van de gegevensmap "$1".
+Controleer de locatie en probeer het opnieuw.',
+       'config-sqlite-dir-unwritable' => 'Het was niet mogelijk in de map "$1" te schrijven.
+Wijzig de rechten zodat de webserver erin kan schrijven en probeer het opnieuw.',
+       'config-sqlite-connection-error' => '$1.
+
+Controleer de map voor gegevens en de databasenaam hieronder en probeer het opnieuw.',
+       'config-sqlite-readonly' => 'Het bestand <code>$1</code> kan niet geschreven worden.',
+       'config-sqlite-cant-create-db' => 'Het was niet mogelijk het databasebestand <code>$1</code> aan te maken.',
+       'config-can-upgrade' => "Er staan al tabellen voor MediaWiki in deze database.
+Klik op '''Doorgaan''' om ze bij te werken naar MediaWiki $1.",
+       'config-upgrade-done' => "Het bijwerken is afgerond.
+
+Uw kunt [$1 uw wiki gebruiken].
+
+Als u uw <code>LocalSettings.php</code> opnieuw wilt aanmaken, klik dan op de knop hieronder.
+Dit is '''niet aan te raden''' tenzij u problemen hebt met uw wiki.",
+       'config-regenerate' => 'LocalSettings.php opnieuw aanmaken →',
+       'config-show-table-status' => 'Het uitvoeren van SHOW TABLE STATUS is mislukt!',
+       'config-unknown-collation' => "'''Waarschiwing:''' de database gebruikt een collatie die niet wordt herkend.",
+       'config-db-web-account' => 'Databasegebruiker voor webtoegang',
+       'config-db-web-help' => 'Selecteer de gebruikersnaam en het wachtwoord die de webserver gebruikt om verbinding te maken met de databaseserver na de installatie.',
+       'config-db-web-account-same' => 'Dezelfde gebruiker gebruiken als voor de installatie',
+       'config-db-web-create' => 'Maak de gebruiker aan als deze nog niet bestaat',
+       'config-db-web-no-create-privs' => 'De gebruiker die u hebt opgegeven voor de installatie heeft niet voldoende rechten om een gebruiker aan te maken.
+De gebruiker die u hier opgeeft moet al bestaan.',
+       'config-mysql-engine' => 'Opslagmethode:',
+       'config-mysql-innodb' => 'InnoDB',
+       'config-mysql-myisam' => 'MyISAM',
+       'config-mysql-engine-help' => "'''InnoDB''' is vrijwel altijd de beste instelling, omdat deze goed omgaat met meerdere verzoeken tegelijkertijd.
+
+'''MyISAM''' is bij een zeer beperkt aantal gebruikers mogelijk sneller, of als de wiki alleen-lezen is.
+MyISAM-databases raken vaker corrupt dan InnoDB-databases.",
+       'config-mysql-charset' => 'Tekenset voor de database:',
+       'config-mysql-binary' => 'Binair',
+       'config-mysql-utf8' => 'UTF-8',
+       'config-site-name' => 'Naam van de wiki:',
+       'config-site-name-help' => 'Deze naam verschijnt in de titelbalk van browsers en op andere plaatsen.',
+       'config-site-name-blank' => 'Geef een naam op voor de site.',
+       'config-project-namespace' => 'Projectnaamruimte:',
+       'config-ns-generic' => 'Project',
+       'config-ns-site-name' => 'Zelfde als de wiki: $1',
+       'config-ns-other' => 'Andere (geen aan welke)',
+       'config-ns-other-default' => 'MijnWiki',
+       'config-project-namespace-help' => "In het kielzog van Wikipedia beheren veel wiki's hun beleidspagina's apart van hun inhoudelijke pagina's in een \"'''projectnaamruimte'''\".
+Alle paginanamen in deze naamruimte beginnen met een bepaald voorvoegsel dat u hier kunt aangeven.
+Dit voorvoegsel wordt meestal afgeleid van de naam van de wiki, maar het kan geen bijzondere tekens bevatten als \"#\" of \":\".",
+       'config-ns-invalid' => 'De aangegeven naamruimte "<nowiki>$1</nowiki>" is ongeldig.
+Geef een andere naamruimte op.',
+       'config-admin-default-username' => 'WikiBeheerder',
+       'config-admin-box' => 'Beheerdersaccount',
+       'config-admin-name' => 'Uw naam:',
+       'config-admin-password' => 'Wachtwoord:',
+       'config-admin-password-confirm' => 'Wachtwoord opnieuw:',
+       'config-admin-help' => 'Voor de gebruikersnaam hier in, bijvoorbeeld "Jan Jansen".
+Dit is de naam die wordt gebruikt om aan de melden bij de wiki.',
+       'config-admin-name-blank' => 'Geef een gebruikersnaam op voor de beheerder.',
+       'config-admin-name-invalid' => 'De opgegeven gebruikersnaam "<nowiki>$1</nowiki>" is ongeldig.
+Kies een andere gebruikersnaam.',
+       'config-admin-password-blank' => 'Voer een wachtwoord voor de beheerder in.',
+       'config-admin-password-same' => 'Het wachtwoord mag niet hetzelfde zijn als de gebruikersnaam.',
+       'config-admin-password-mismatch' => 'De twee door u ingevoerde wachtwoorden komen niet overeen.',
+       'config-admin-email' => 'E-mailadres:',
+       'config-admin-email-help' => "Voer hier een e-mailadres in om e-mail te kunnen ontvangen van andere gebruikers op de wiki, uw wachtwoord opnieuw in te stellen en op de hoogte te worden gehouden van wijzigingen van pagina's op uw volglijst.",
+       'config-subscribe' => 'Abonneren op de [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce mailinglijst releaseaankondigen].',
+       'config-subscribe-help' => 'Dit is een mailinglijst met een laag volume voor aankondigingen van nieuwe versies, inclusief belangrijke aankondigingen met betrekking tot beveiliging.
+Abonneer uzelf erop en werk uw MediaWiki-installatie bij als er nieuwe versies uitkomen.',
+       'config-almost-done' => 'U bent bijna klaar!
+Als u wilt kunt u de overige instellingen overslaan en de wiki nu installeren.',
+       'config-optional-continue' => 'Meer vragen',
+       'config-optional-skip' => 'Installeer de wiki',
+       'config-profile' => 'Gebruikersrechtenprofiel:',
+       'config-profile-wiki' => 'Traditionele wiki',
+       'config-profile-no-anon' => 'Gebruiker aanmaken verplicht',
+       'config-profile-private' => 'Privéwiki',
+       'config-license' => 'Auteursrechten en licentie:',
+       'config-license-none' => 'Geen licentie in de voettekst',
+       'config-license-gfdl-old' => 'GNU Free Documentation License 1.2 of hoger',
+       'config-license-gfdl-current' => 'GNU Free Documentation License 1.3 of hoger',
+       'config-license-pd' => 'Publiek domein',
+       'config-license-cc-choose' => 'Een Creative Commons-licentie',
+       'config-email-settings' => 'E-mailinstellingen',
+       'config-enable-email' => 'Uitgaande e-mail inschakelen',
+       'config-enable-email-help' => "Als u wilt dat e-mailen mogelijk is, dan moeten [http://www.php.net/manual/en/mail.configuration.php PHP's e-mailinstellingen] correct zijn.
+Als u niet wilt dat e-mailen mogelijk is, dan kunt u de instellingen hier uitschakelen.",
+       'config-email-user' => 'E-mail tussen gebruikers inschakelen',
+       'config-email-user-help' => 'Gebruikers toestaan e-mail aan elkaar te verzenden als dit in de voorkeuren is ingesteld',
+       'config-email-usertalk' => 'Gebruikersoverlegnotificatie inschakelen',
+       'config-email-usertalk-help' => 'Gebruikers toestaan notificaties te ontvangen bij wijzigingen op de eigen overlegpagina als dit in de voorkeuren is ingesteld',
+       'config-email-watchlist' => 'Volglijstnotificatie inschakelen',
+       'config-email-watchlist-help' => "Gebruikers toestaan notificaties te ontvangen bij wijzigingen van pagina's op hun volglijst als dit in de voorkeuren is ingesteld",
+       'config-email-auth' => 'E-mailbevestiging inschakelen',
+       'config-email-auth-help' => "Als deze instelling actief is, moeten gebruikers hun e-mailadres bevestigen via een verwijziging die ze per e-mail wordt toegezonden.
+Alleen bevestigde e-mailadressen kunnen e-mail ontvangen van andere gebruikers of wijzigingsnotificaties ontvangen.
+Het inschakelen van deze instelling is '''aan te raden''' voor openbare wiki's vanwege de mogelijkheden voor misbruik van e-mailmogelijkheden.",
+       'config-email-sender' => 'E-mailadres voor antwoorden:',
+       'config-email-sender-help' => 'Voer het e-mailadres in dat u wilt gebruiken als antwoordadres voor uitgaande e-mail.
+Als een e-mail niet bezorgd kan worden, wordt dat op dit e-mailadres gemeld.
+Veel mailservers vereisen dat tenminste het domein bestaat.',
+       'config-upload-settings' => 'Afbeeldingen en bestanden uploaden',
+       'config-upload-enable' => 'Uploaden van bestanden inschakelen',
+       'config-upload-deleted' => 'Map voor verwijderde bestanden:',
+       'config-upload-deleted-help' => 'Kies een map waarin verwijderde bestanden gearchiveerd kunnen worden.
+Idealiter is deze map niet via het web te benaderen.',
+       'config-logo' => 'URL voor logo:',
+       'config-logo-help' => 'Het standaarduiterlijk van MediaWiki bevat ruimte voor een logo van 135x135 pixels in de linker bovenhoek.
+Upload een afbeelding met de juiste afmetingen en voer de URL hier in.
+
+Als u geen logo wilt gebruiken, kunt u dit veld leeg laten.',
+       'config-cc-error' => 'De licentiekiezer van Creative Commons heeft geen resultaat opgeleverd.
+Voer de licentie handmatig in.',
+       'config-cc-not-chosen' => 'Kies alstublieft de Creative Commons-licentie die u wilt gebruiken en klik op "doorgaan".',
+       'config-advanced-settings' => 'Gevorderde instellingen',
+       'config-cache-options' => 'Instellingen voor het cachen van objecten:',
+       'config-cache-help' => 'Het cachen van objecten wordt gebruikt om de snelheid van MediaWiki te verbeteren door vaak gebruikte gegevens te bewaren.
+Middelgrote tot grote websites wordt geadviseerd dit in te schakelen en ook kleine sites merken de voordelen.',
+       'config-cache-none' => 'Niets cachen.
+Er gaat geen functionaliteit verloren, maar dit kan invloed hebben op de snelheid.',
+       'config-cache-accel' => 'Cachen van objecten via PHP (APC, eAccelerator or XCache)',
+       'config-cache-memcached' => 'Memcached gebruiken (dit vereist aanvullende instellingen)',
+       'config-cache-db' => 'Gegevens cachen in de database',
+       'config-cache-anything' => 'MediaWiki zal proberen de gegevens te cachen waar mogelijk, behalve in Memcached, tenzij expliciet aangegeven',
+       'config-memcached-servers' => 'Memcachedservers:',
+       'config-memcached-help' => "Lijst met IP-adressen te gebruiken voor Memcached.
+Deze moeten worden gescheiden met komma's en geef de poort op die moet worden gebruikt (bijvoorbeeld: 1.2.3.4:56, 7.8.9.10:11).",
+       'config-extensions' => 'Uitbreidingen',
+       'config-extensions-help' => 'De bovenstaande uitbreidingen zijn aangetroffen in de map <code>./extensions</code>.
+
+Mogelijk moet u aanvullende instellingen maken, maar u kunt deze uitbreidingen nu inschakelen.',
+       'config-install-step-done' => 'Afgerond',
+       'config-install-step-failed' => 'Mislukt',
+       'config-install-extensions' => 'Inclusief uitbreidingen',
+       'config-install-database' => 'Database inrichten',
+       'config-install-pg-schema-failed' => 'Het aanmaken van de tabellen is mislukt.
+Zorg dat de gebruiker "$1" in het schema "$2" mag schrijven.',
+       'config-install-tables' => 'Tabellen aanmaken',
+       'config-install-interwiki-sql' => 'Het bestand <code>interwiki.sql</code> is niet aangetroffen',
+       'config-install-secretkey' => 'Geheime sleutel aanmaken',
+       'config-insecure-secretkey' => 'Waarschuwing: het was niet mogelijk een veilige <code>$wgSecretKey</code> aan te maken.
+Overweeg deze handmatig te wijzigen.',
+       'config-install-sysop' => 'Gebruiker voor beheerder aanmaken',
+       'config-install-localsettings' => '<code>LocalSettings.php</code> aanmaken',
+       'config-install-localsettings-unwritable' => "'''Waarschuwing:''' het was niet mogelijk <code>LocalSettings.php</code> weg te schrijven.
+Maak dit bestand zelf aan met de volgende inhoud:",
+       'config-install-done-moved' => "'''Gefeliciteerd!'''
+U hebt MediaWiki geïnstalleerd.
+
+[$1 Naar uw wiki]",
+);
+
+/**
+ * Russian (Русский)
+ * @author MaxSem
+ */
+$messages['ru'] = array(
+       'config-title'                  => 'Установка MediaWiki $1',
+       'config-page-language'          => 'Язык',
+       'config-admin-default-username' => 'ВикиАдминистратор',
+);
\ No newline at end of file
diff --git a/includes/installer/Installer.php b/includes/installer/Installer.php
new file mode 100644 (file)
index 0000000..6abdbf3
--- /dev/null
@@ -0,0 +1,932 @@
+<?php
+
+/**
+ * Base installer class
+ * Handles everything that is independent of user interface
+ */
+abstract class Installer {
+       var $settings, $output;
+
+       /**
+        * MediaWiki configuration globals that will eventually be passed through 
+        * to LocalSettings.php. The names only are given here, the defaults 
+        * typically come from DefaultSettings.php.
+        * @protected
+        */
+       var $defaultVarNames = array(
+               'wgSitename',
+               'wgPasswordSender',
+               'wgLanguageCode', 
+               'wgRightsIcon',
+               'wgRightsText',
+               'wgRightsUrl',
+               'wgMainCacheType',
+               'wgEnableEmail',
+               'wgEnableUserEmail',
+               'wgEnotifUserTalk',
+               'wgEnotifWatchlist',
+               'wgEmailAuthentication',
+               'wgDBtype',
+               'wgDiff3',
+               'wgImageMagickConvertCommand',
+               'IP',
+               'wgScriptPath',
+               'wgScriptExtension',
+               'wgMetaNamespace',
+//             'wgDeletedDirectory',
+               'wgEnableUploads',
+               'wgLogo',
+               'wgShellLocale',
+               'wgSecretKey',
+       );
+
+       /**
+        * Variables that are stored alongside globals, and are used for any 
+        * configuration of the installation process aside from the MediaWiki 
+        * configuration. Map of names to defaults.
+        * @protected
+        */
+       var $internalDefaults = array(
+               '_UserLang' => 'en',
+               '_Environment' => false,
+               '_CompiledDBs' => array(),
+               '_SafeMode' => false,
+               '_RaiseMemory' => false,
+               '_UpgradeDone' => false,
+               '_Caches' => array(),
+               '_InstallUser' => 'root',
+               '_InstallPassword' => '',
+               '_SameAccount' => true,
+               '_CreateDBAccount' => false,
+               '_NamespaceType' => 'site-name',
+               '_AdminName' => null, // will be set later, when the user selects language
+               '_AdminPassword' => '',
+               '_AdminPassword2' => '',
+               '_AdminEmail' => '',
+               '_Subscribe' => false,
+               '_SkipOptional' => 'continue',
+               '_RightsProfile' => 'wiki',
+               '_LicenseCode' => 'none',
+               '_CCDone' => false,
+               '_Extensions' => array(),
+               '_MemCachedServers' => '',
+       );
+
+       /**
+        * Known database types. These correspond to the class names <type>Installer,
+        * and are also MediaWiki database types valid for $wgDBtype.
+        *
+        * To add a new type, create a <type>Installer class and a Database<type> 
+        * class, and add a config-type-<type> message to MessagesEn.php.
+        * @private
+        */
+       var $dbTypes = array(
+               'mysql',
+               'postgres',
+               'sqlite',
+               'oracle'
+       );
+
+       /**
+        * Minimum memory size in MB
+        */
+       private $minMemorySize = 50;
+
+       /**
+        * Cached DB installer instances, access using getDBInstaller()
+        * @private
+        */
+       var $dbInstallers = array();
+
+       /**
+        * A list of environment check methods called by doEnvironmentChecks(). 
+        * These may output warnings using showMessage(), and/or abort the 
+        * installation process by returning false.
+        * @protected
+        */
+       var $envChecks = array( 
+               'envLatestVersion',
+               'envCheckDB',
+               'envCheckRegisterGlobals',
+               'envCheckMagicQuotes',
+               'envCheckMagicSybase',
+               'envCheckMbstring',
+               'envCheckZE1',
+               'envCheckSafeMode',
+               'envCheckXML',
+               'envCheckPCRE',
+               'envCheckMemory',
+               'envCheckCache',
+               'envCheckDiff3',
+               'envCheckGraphics',
+               'envCheckPath',
+               'envCheckWriteableDir',
+               'envCheckExtension',
+               'envCheckShellLocale',
+               'envCheckUploadsDirectory',
+       );
+
+       var $installSteps = array(
+               'database',
+               'tables',
+               'secretkey',
+               'sysop',
+               'localsettings',
+       );
+
+       /**
+        * Known object cache types and the functions used to test for their existence
+        * @protected
+        */
+       var $objectCaches = array( 
+               'xcache' => 'xcache_get',
+               'apc' => 'apc_fetch',
+               'eaccel' => 'eaccelerator_get'
+       );
+
+       /**
+        * User rights profiles
+        */
+       var $rightsProfiles = array(
+               'wiki' => array(),
+               'no-anon' => array(
+                       '*' => array( 'edit' => false )
+               ),
+               'fishbowl' => array(
+                       '*' => array( 
+                               'createaccount' => false,
+                               'edit' => false,
+                       ),
+               ),
+               'private' => array(
+                       '*' => array(
+                               'createaccount' => false,
+                               'edit' => false,
+                               'read' => false,
+                       ),
+               ),
+       );
+
+       /**
+        * License types
+        */
+       var $licenses = array(
+               'none' => array(
+                       'url' => '',
+                       'icon' => '',
+                       'text' => ''
+               ),
+               'pd' => array(
+                       'url' => 'http://creativecommons.org/licenses/publicdomain/',
+                       'icon' => '{$wgStylePath}/common/images/public-domain.png',
+               ),
+               'gfdl-old' => array(
+                       'url' => 'http://www.gnu.org/licenses/old-licenses/fdl-1.2.html',
+                       'icon' => '{$wgStylePath}/common/images/gnu-fdl.png',
+               ),
+               'gfdl-current' => array(
+                       'url' => 'http://www.gnu.org/copyleft/fdl.html',
+                       'icon' => '{$wgStylePath}/common/images/gnu-fdl.png',
+               ),
+               'cc-choose' => array(
+                       // details will be filled in by the selector
+                       'url' => '', 
+                       'icon' => '',
+                       'text' => '',
+               ),
+       );
+       /**
+        * Cached Title and ParserOptions used by parse()
+        * @private
+        */
+       var $parserTitle, $parserOptions;
+
+       /**
+        * Constructor, always call this from child classes
+        */
+       function __construct() {
+               $this->settings = $this->internalDefaults;
+               foreach ( $this->defaultVarNames as $var ) {
+                       $this->settings[$var] = $GLOBALS[$var];
+               }
+               foreach ( $this->dbTypes as $type ) {
+                       $installer = $this->getDBInstaller( $type );
+                       if ( !$installer->isCompiled() ) {
+                               continue;
+                       }
+                       $defaults = $installer->getGlobalDefaults();
+                       foreach ( $installer->getGlobalNames() as $var ) {
+                               if ( isset( $defaults[$var] ) ) {
+                                       $this->settings[$var] = $defaults[$var];
+                               } else {
+                                       $this->settings[$var] = $GLOBALS[$var];
+                               }
+                       }
+               }
+
+               $this->parserTitle = Title::newFromText( 'Installer' );
+               $this->parserOptions = new ParserOptions;
+               $this->parserOptions->setEditSection( false );
+       }
+
+       /**
+        * UI interface for displaying a short message
+        * The parameters are like parameters to wfMsg().
+        * The messages will be in wikitext format, which will be converted to an 
+        * output format such as HTML or text before being sent to the user.
+        */
+       abstract function showMessage( $msg /*, ... */ );
+
+       abstract function showStatusError( $status );
+
+       /**
+        * Get a list of known DB types
+        */
+       function getDBTypes() {
+               return $this->dbTypes;
+       }
+
+       /**
+        * Get an instance of InstallerDBType for the specified DB type
+        * @param $type Mixed: DB installer for which is needed, false to use default
+        */
+       function getDBInstaller( $type = false ) {
+               if ( !$type ) {
+                       $type = $this->getVar( 'wgDBtype' );
+               }
+               if ( !isset( $this->dbInstallers[$type] ) ) {
+                       $class = ucfirst( $type ). 'Installer';
+                       $this->dbInstallers[$type] = new $class( $this );
+               }
+               return $this->dbInstallers[$type];
+       }
+
+       /**
+        * Do initial checks of the PHP environment. Set variables according to 
+        * the observed environment.
+        *
+        * It's possible that this may be called under the CLI SAPI, not the SAPI
+        * that the wiki will primarily run under. In that case, the subclass should
+        * initialise variables such as wgScriptPath, before calling this function.
+        *
+        * Under the web subclass, it can already be assumed that PHP 5+ is in use 
+        * and that sessions are working.
+        */
+       function doEnvironmentChecks() {
+               $this->showMessage( 'config-env-php', phpversion() );
+
+               $good = true;
+               foreach ( $this->envChecks as $check ) {
+                       $status = $this->$check();
+                       if ( $status === false ) {
+                               $good = false;
+                       }
+               }
+               $this->setVar( '_Environment', $good );
+               if ( $good ) {
+                       $this->showMessage( 'config-env-good' );
+               } else {
+                       $this->showMessage( 'config-env-bad' );
+               }
+               return $good;
+       }
+
+       /**
+        * Get an MW configuration variable, or internal installer configuration variable.
+        * The defaults come from $GLOBALS (ultimately DefaultSettings.php).
+        * Installer variables are typically prefixed by an underscore.
+        */
+       function getVar( $name, $default = null ) {
+               if ( !isset( $this->settings[$name] ) ) {
+                       return $default;
+               } else {
+                       return $this->settings[$name];
+               }
+       }
+
+       /**
+        * Set a MW configuration variable, or internal installer configuration variable.
+        */
+       function setVar( $name, $value ) {
+               $this->settings[$name] = $value;
+       }
+
+       /**
+        * Exports all wg* variables stored by the installer into global scope
+        */
+       function exportVars() {
+               foreach ( $this->settings as $name => $value ) {
+                       if ( substr( $name, 0, 2 ) == 'wg' ) {
+                               $GLOBALS[$name] = $value;
+                       }
+               }
+       }
+
+       /**
+        * Get a fake password for sending back to the user in HTML.
+        * This is a security mechanism to avoid compromise of the password in the
+        * event of session ID compromise.
+        */
+       function getFakePassword( $realPassword ) {
+               return str_repeat( '*', strlen( $realPassword ) );
+       }
+
+       /**
+        * Set a variable which stores a password, except if the new value is a 
+        * fake password in which case leave it as it is.
+        */
+       function setPassword( $name, $value ) {
+               if ( !preg_match( '/^\*+$/', $value ) ) {
+                       $this->setVar( $name, $value );
+               }
+       }
+
+       /**
+        * Returns true if dl() can be used
+        */
+       function haveDl() {
+               return function_exists( 'dl' )
+                       && is_callable( 'dl' )
+                       && wfIniGetBool( 'enable_dl' )
+                       && !wfIniGetBool( 'safe_mode' );
+       }
+       
+       /** Check if we're installing the latest version */
+       function envLatestVersion() {
+               global $wgVersion;
+               $latestInfoUrl = 'http://www.mediawiki.org/w/api.php?action=mwreleases&format=json';
+               $latestInfo = Http::get( $latestInfoUrl );
+               if( !$latestInfo ) {
+                       $this->showMessage( 'config-env-latest-can-not-check', $latestInfoUrl );
+                       return;
+               }
+               $latestInfo = FormatJson::decode($latestInfo);
+               if ($latestInfo === false || !isset( $latestInfo->mwreleases ) ) {
+                       # For when the request is successful but there's e.g. some silly man in
+                       # the middle firewall blocking us, e.g. one of those annoying airport ones
+                       $this->showMessage( 'config-env-latest-data-invalid', $latestInfoUrl );
+                       return;
+               }
+               foreach( $latestInfo->mwreleases as $rel ) {
+                       if( isset( $rel->current ) )
+                               $currentVersion = $rel->version;
+               }
+               if( version_compare( $wgVersion, $currentVersion, '<' ) ) {
+                       $this->showMessage( 'config-env-latest-old' );
+                       $this->showHelpBox( 'config-env-latest-help', $wgVersion, $currentVersion ); 
+               } elseif( version_compare( $wgVersion, $currentVersion, '>' ) ) {
+                       $this->showMessage( 'config-env-latest-new' );
+               }
+               $this->showMessage( 'config-env-latest-ok' );
+       }
+
+       /** Environment check for DB types */
+       function envCheckDB() {
+               $compiledDBs = array();
+               $haveDl = $this->haveDl();
+               $goodNames = array();
+               $allNames = array();
+               foreach ( $this->dbTypes as $name ) {
+                       $db = $this->getDBInstaller( $name );
+                       $readableName = wfMsg( 'config-type-' . $name );
+                       if ( $db->isCompiled() ) {
+                               $compiledDBs[] = $name;
+                               $goodNames[] = $readableName;
+                       }
+                       $allNames[] = $readableName;
+               }
+               $this->setVar( '_CompiledDBs', $compiledDBs );
+
+               global $wgLang;
+               if ( !$compiledDBs ) {
+                       $this->showMessage( 'config-no-db' );
+                       $this->showHelpBox( 'config-no-db-help', $wgLang->commaList( $allNames ) );
+                       return false;
+               }
+               $this->showMessage( 'config-have-db', $wgLang->commaList( $goodNames ) );
+       }
+
+       /** Environment check for register_globals */
+       function envCheckRegisterGlobals() {
+               if( wfIniGetBool( "magic_quotes_runtime" ) ) {
+                       $this->showMessage( 'config-register-globals' );
+               }
+       }
+
+       /** Environment check for magic_quotes_runtime */
+       function envCheckMagicQuotes() {
+               if( wfIniGetBool( "magic_quotes_runtime" ) ) {
+                       $this->showMessage( 'config-magic-quotes-runtime' );
+                       return false;
+               }
+       }
+
+       /** Environment check for magic_quotes_sybase */
+       function envCheckMagicSybase() {
+               if ( wfIniGetBool( 'magic_quotes_sybase' ) ) {
+                       $this->showMessage( 'config-magic-quotes-sybase' );
+                       return false;
+               }
+       }
+
+       /* Environment check for mbstring.func_overload */
+       function envCheckMbstring() {
+               if ( wfIniGetBool( 'mbstring.func_overload' ) ) {
+                       $this->showMessage( 'config-mbstring' );
+                       return false;
+               }
+       }
+
+       /** Environment check for zend.ze1_compatibility_mode */
+       function envCheckZE1() {
+               if ( wfIniGetBool( 'zend.ze1_compatibility_mode' ) ) {
+                       $this->showMessage( 'config-ze1' );
+                       return false;
+               }
+       }
+
+       /** Environment check for safe_mode */
+       function envCheckSafeMode() {
+               if ( wfIniGetBool( 'safe_mode' ) ) {
+                       $this->setVar( '_SafeMode', true );
+                       $this->showMessage( 'config-safe-mode' );
+               }
+       }
+
+       /** Environment check for the XML module */
+       function envCheckXML() {
+               if ( !function_exists( "utf8_encode" ) ) {
+                       $this->showMessage( 'config-xml-bad' );
+                       return false;
+               }
+               $this->showMessage( 'config-xml-good' );
+       }
+
+       /** Environment check for the PCRE module */
+       function envCheckPCRE() {
+               if ( !function_exists( 'preg_match' ) ) {
+                       $this->showMessage( 'config-pcre' );
+                       return false;
+               }
+       }
+
+       /** Environment check for available memory */
+       function envCheckMemory() {
+               $limit = ini_get( 'memory_limit' );
+               if ( !$limit || $limit == -1 ) {
+                       $this->showMessage( 'config-memory-none' );
+                       return true;
+               }
+               $n = intval( $limit );
+               if( preg_match( '/^([0-9]+)[Mm]$/', trim( $limit ), $m ) ) {
+                       $n = intval( $m[1] * (1024*1024) );
+               }
+               if( $n < $this->minMemorySize*1024*1024 ) {
+                       $newLimit = "{$this->minMemorySize}M";
+                       if( false === ini_set( "memory_limit", $newLimit ) ) {
+                               $this->showMessage( 'config-memory-bad', $limit );
+                       } else {
+                               $this->showMessage( 'config-memory-raised', $limit, $newLimit );
+                               $this->setVar( '_RaiseMemory', true );
+                       }
+               } else {
+                       $this->showMessage( 'config-memory-ok', $limit );
+               }
+       }
+
+       /** Environment check for compiled object cache types */
+       function envCheckCache() {
+               $caches = array();
+               foreach ( $this->objectCaches as $name => $function ) {
+                       if ( function_exists( $function ) ) {
+                               $caches[$name] = true;
+                               $this->showMessage( 'config-' . $name );
+                       }
+               }
+               if ( !$caches ) {
+                       $this->showMessage( 'config-no-cache' );
+               }
+               $this->setVar( '_Caches', $caches );
+       }
+
+       /** Search for GNU diff3 */
+       function envCheckDiff3() {
+               $paths = array_merge(
+                       array(
+                               "/usr/bin",
+                               "/usr/local/bin",
+                               "/opt/csw/bin",
+                               "/usr/gnu/bin",
+                               "/usr/sfw/bin" ),
+                       explode( PATH_SEPARATOR, getenv( "PATH" ) ) );
+               $names = array( "gdiff3", "diff3", "diff3.exe" );
+
+               $versionInfo = array( '$1 --version 2>&1', 'diff3 (GNU diffutils)' );
+               $haveDiff3 = false;
+               foreach ( $paths as $path ) {
+                       $exe = $this->locateExecutable( $path, $names, $versionInfo );
+                       if ($exe !== false) {
+                               $this->setVar( 'wgDiff3', $exe );
+                               $haveDiff3 = true;
+                               break;
+                       }
+               }
+               if ( $haveDiff3 ) {
+                       $this->showMessage( 'config-diff3-good', $exe );
+               } else {
+                       $this->setVar( 'wgDiff3', false );
+                       $this->showMessage( 'config-diff3-bad' );
+               }
+       }
+
+       /**
+        * Search a path for any of the given executable names. Returns the 
+        * executable name if found. Also checks the version string returned 
+        * by each executable
+        *
+        * @param string $path Path to search
+        * @param array $names Array of executable names
+        * @param string $versionInfo Array with two members:
+        *       0 => Command to run for version check, with $1 for the path
+        *       1 => String to compare the output with
+        *
+        * If $versionInfo is not false, only executables with a version 
+        * matching $versionInfo[1] will be returned.
+        */
+       function locateExecutable( $path, $names, $versionInfo = false ) {
+               if (!is_array($names))
+                       $names = array($names);
+
+               foreach ($names as $name) {
+                       $command = "$path/$name";
+                       if ( @file_exists( $command ) ) {
+                               if ( !$versionInfo )
+                                       return $command;
+
+                               $file = str_replace( '$1', $command, $versionInfo[0] );
+                               if ( strstr( wfShellExec( $file ), $versionInfo[1]) !== false )
+                                       return $command;
+                       }
+               }
+               return false;
+       }
+
+       /** Environment check for ImageMagick and GD */
+       function envCheckGraphics() {
+               $imcheck = array( "/usr/bin", "/opt/csw/bin", "/usr/local/bin", "/sw/bin", "/opt/local/bin" );
+               foreach( $imcheck as $dir ) {
+                       $im = "$dir/convert";
+                       if( @file_exists( $im ) ) {
+                               $this->showMessage( 'config-imagemagick', $im );
+                               $this->setVar( 'wgImageMagickConvertCommand', $im );
+                               return true;
+                       }
+               }
+               if ( function_exists( 'imagejpeg' ) ) {
+                       $this->showMessage( 'config-gd' );
+                       return true;
+               }
+               $this->showMessage( 'no-scaling' );
+       }
+
+       /** Environment check for setting $IP and $wgScriptPath */
+       function envCheckPath() {
+               $IP = dirname( dirname( dirname( __FILE__ ) ) );
+               $this->setVar( 'IP', $IP );
+               $this->showMessage( 'config-dir', $IP );
+
+               // PHP_SELF isn't available sometimes, such as when PHP is CGI but
+               // cgi.fix_pathinfo is disabled. In that case, fall back to SCRIPT_NAME
+               // to get the path to the current script... hopefully it's reliable. SIGH
+               if ( !empty( $_SERVER['PHP_SELF'] ) ) {
+                       $path = $_SERVER['PHP_SELF'];
+               } elseif ( !empty( $_SERVER['SCRIPT_NAME'] ) ) {
+                       $path = $_SERVER['SCRIPT_NAME'];
+               } elseif ( $this->getVar( 'wgScriptPath' ) ) {
+                       // Some kind soul has set it for us already (e.g. debconf)
+                       return true;
+               } else {
+                       $this->showMessage( 'config-no-uri' );
+                       return false;
+               }
+               $uri = preg_replace( '{^(.*)/config.*$}', '$1', $path );
+               $this->setVar( 'wgScriptPath', $uri );
+               $this->showMessage( 'config-uri', $uri );
+       }
+
+       /** Environment check for writable config/ directory */
+       function envCheckWriteableDir() {
+               $ipDir = $this->getVar( 'IP' );
+               $configDir = $ipDir . '/config';
+               if( !is_writeable( $configDir ) ) {
+                       $webserverGroup = self::maybeGetWebserverPrimaryGroup();
+                       if ( $webserverGroup !== null ) {
+                               $this->showMessage( 'config-dir-not-writable-group', $ipDir, $webserverGroup );
+                       } else {
+                               $this->showMessage( 'config-dir-not-writable-nogroup', $ipDir, $webserverGroup );
+                       }
+                       return false;
+               }
+       }
+
+       /** Environment check for setting the preferred PHP file extension */
+       function envCheckExtension() {
+               // FIXME: detect this properly
+               if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) {
+                       $ext = 'php5';
+               } else {
+                       $ext = 'php';
+               }
+               $this->setVar( 'wgScriptExtension', ".$ext" );
+               $this->showMessage( 'config-file-extension', $ext );
+       }
+
+       function envCheckShellLocale() {
+               # Give up now if we're in safe mode or open_basedir
+               # It's theoretically possible but tricky to work with
+               if ( wfIniGetBool( "safe_mode" ) || ini_get( 'open_basedir' ) || !function_exists( 'exec' ) ) {
+                       return true;
+               }
+
+               $os = php_uname( 's' );
+               $supported = array( 'Linux', 'SunOS', 'HP-UX' ); # Tested these
+               if ( !in_array( $os, $supported ) ) {
+                       return true;
+               }
+
+               # Get a list of available locales
+               $lines = $ret = false;
+               exec( '/usr/bin/locale -a', $lines, $ret );
+               if ( $ret ) {
+                       return true;
+               }
+
+               $lines = wfArrayMap( 'trim', $lines );
+               $candidatesByLocale = array();
+               $candidatesByLang = array();
+               foreach ( $lines as $line ) {
+                       if ( $line === '' ) {
+                               continue;
+                       }
+                       if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) {
+                               continue;
+                       }
+                       list( $all, $lang, $territory, $charset, $modifier ) = $m;
+                       $candidatesByLocale[$m[0]] = $m;
+                       $candidatesByLang[$lang][] = $m;
+               }
+
+               # Try the current value of LANG
+               if ( isset( $candidatesByLocale[ getenv( 'LANG' ) ] ) ) {
+                       $this->setVar( 'wgShellLocale', getenv( 'LANG' ) );
+                       $this->showMessage( 'config-shell-locale', getenv( 'LANG' ) );
+                       return true;
+               }
+
+               # Try the most common ones
+               $commonLocales = array( 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' );
+               foreach ( $commonLocales as $commonLocale ) {
+                       if ( isset( $candidatesByLocale[$commonLocale] ) ) {
+                               $this->setVar( 'wgShellLocale', $commonLocale );
+                               $this->showMessage( 'config-shell-locale', $commonLocale );
+                               return true;
+                       }
+               }
+
+               # Is there an available locale in the Wiki's language?
+               $wikiLang = $this->getVar( 'wgLanguageCode' );
+               if ( isset( $candidatesByLang[$wikiLang] ) ) {
+                       $m = reset( $candidatesByLang[$wikiLang] );
+                       $this->setVar( 'wgShellLocale', $m[0] );
+                       $this->showMessage( 'config-shell-locale', $m[0] );
+                       return true;
+               }
+
+               # Are there any at all?
+               if ( count( $candidatesByLocale ) ) {
+                       $m = reset( $candidatesByLocale );
+                       $this->setVar( 'wgShellLocale', $m[0] );
+                       $this->showMessage( 'config-shell-locale', $m[0] );
+                       return true;
+               }
+
+               # Give up
+               return true;
+       }
+       
+       function envCheckUploadsDirectory() {
+               global $IP, $wgServer;
+               $dir = $IP . '/images/';
+               $url = $wgServer . $this->getVar( 'wgScriptPath' ) . '/images/';
+               $safe = !$this->dirIsExecutable( $dir, $url );
+               if ( $safe ) {
+                       $this->showMessage( 'config-uploads-safe' );
+               } else {
+                       $this->showMessage( 'config-uploads-not-safe', $dir );
+               }
+       }
+
+       /**
+        * Checks if scripts located in the given directory can be executed via the given URL
+        */
+       function dirIsExecutable( $dir, $url ) {
+               $scriptTypes = array(
+                       'php' => array(
+                               "<?php echo 'ex' . 'ec';",
+                               "#!/var/env php5\n<?php echo 'ex' . 'ec';",
+                       ),
+               );
+               // it would be good to check other popular languages here, but it'll be slow
+
+               wfSuppressWarnings();
+               foreach ( $scriptTypes as $ext => $contents ) {
+                       foreach ( $contents as $source ) {
+                               $file = 'exectest.' . $ext;
+                               if ( !file_put_contents( $dir . $file, $source ) ) {
+                                       break;
+                               }
+                               $text = Http::get( $url . $file );
+                               unlink( $dir . $file );
+                               if ( $text == 'exec' ) {
+                                       wfRestoreWarnings();
+                                       return $ext;
+                               }
+                       }
+               }
+               wfRestoreWarnings();
+               return false;
+       }
+
+       /**
+        * Convert wikitext $text to HTML.
+        *
+        * This is potentially error prone since many parser features require a complete
+        * installed MW database. The solution is to just not use those features when you 
+        * write your messages. This appears to work well enough. Basic formatting and
+        * external links work just fine.
+        *
+        * But in case a translator decides to throw in a #ifexist or internal link or 
+        * whatever, this function is guarded to catch attempted DB access and to present 
+        * some fallback text.
+        *
+        * @param string $text
+        * @return string
+        */
+       function parse( $text, $lineStart = false ) {
+               global $wgParser;
+               try {
+                       $out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
+                       $html = $out->getText();
+               } catch ( InstallerDBAccessError $e ) {
+                       $html = '<!--DB access attempted during parse-->  ' . htmlspecialchars( $text );
+                       if ( !empty( $this->debug ) ) {
+                               $html .= "<!--\n" . $e->getTraceAsString() . "\n-->";
+                       }
+               }
+               return $html;
+       }
+
+       /**
+        * Extension tag hook for a documentation link
+        */
+       function docLink( $linkText, $attribs, $parser ) {
+               $url = $this->getDocUrl( $attribs['href'] );
+               return '<a href="' . htmlspecialchars( $url ) . '">' . 
+                       htmlspecialchars( $linkText ) . 
+                       '</a>';
+       }
+
+       /**
+        * Overridden by WebInstaller to provide lastPage parameters
+        */
+       protected function getDocUrl( $page ) {
+               return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $attribs['href'] );
+       }
+
+       public function findExtensions() {
+               if( $this->getVar( 'IP' ) === null ) {
+                       return false;
+               }
+               $exts = array();
+               $dir = $this->getVar( 'IP' ) . '/extensions';
+               $dh = opendir( $dir );
+               while ( ( $file = readdir( $dh ) ) !== false ) {
+                       if( file_exists( "$dir/$file/$file.php" ) ) {
+                               $exts[] = $file;
+                       }
+               }
+               $this->setVar( '_Extensions', $exts );
+               return $exts;
+       }
+
+       public function getInstallSteps() {
+               if( $this->getVar( '_UpgradeDone' ) ) {
+                       $this->installSteps = array( 'localsettings' );
+               }
+               if( count( $this->getVar( '_Extensions' ) ) ) {
+                       $this->installSteps = array_unshift( $this->installSteps, 'extensions' );
+               }
+               return $this->installSteps;
+       }
+
+       public function installExtensions() {
+               global $wgHooks, $wgAutoloadClasses;
+               $exts = $this->getVar( '_Extensions' );
+               $path = $this->getVar( 'IP' ) . '/extensions';
+               foreach( $exts as $e ) {
+                       require( "$path/$e/$e.php" );
+               }
+               return Status::newGood();
+       }
+
+       public function installDatabase() {
+               $installer = $this->getDBInstaller( $this->getVar( 'wgDBtype' ) );
+               $status = $installer->setupDatabase();
+               return $status;
+       }
+
+       public function installTables() {
+               $installer = $this->getDBInstaller();
+               $status = $installer->createTables();
+               if( $status->isGood() ) {
+                       LBFactory::enableBackend();
+               }
+               return $status;
+       }
+
+       public function installSecretKey() {
+               $file = wfIsWindows() ? null : @fopen( "/dev/urandom", "r" );
+               if ( $file ) {
+                       $secretKey = bin2hex( fread( $file, 32 ) );
+                       fclose( $file );
+               } else {
+                       $secretKey = "";
+                       for ( $i=0; $i<8; $i++ ) {
+                               $secretKey .= dechex(mt_rand(0, 0x7fffffff));
+                       }
+                       $this->output->addWarningMsg( 'config-insecure-secretkey' );
+               }
+               $this->setVar( 'wgSecretKey', $secretKey );
+               return Status::newGood();
+       }
+
+       public function installSysop() {
+               $name = $this->getVar( '_AdminName' );
+               $user = User::newFromName( $name );
+               if ( !$user ) {
+                       // we should've validated this earlier anyway!
+                       return Status::newFatal( 'config-admin-error-user', $name );
+               }
+               if ( $user->idForName() == 0 ) {
+                       $user->addToDatabase();
+                       try {
+                               $user->setPassword( $this->getVar( '_AdminPassword' ) );
+                       } catch( PasswordError $pwe ) {
+                               return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() );
+                       }
+                       $user->saveSettings();
+                       $user->addGroup( 'sysop' );
+                       $user->addGroup( 'bureaucrat' );
+               }
+               return Status::newGood();
+       }
+
+       public function installLocalsettings() {
+               $localSettings = new LocalSettingsGenerator( $this );
+               $ok = $localSettings->writeLocalSettings();
+
+               # TODO: Make writeLocalSettings() itself not warn, but instead return
+               # a Status object to us to pass along.
+               if ( $ok ) {
+                       return Status::newGood();
+               } else {
+                       return Status::newFatal();
+               }
+       }
+
+       /*
+        * On POSIX systems return the primary group of the webserver we're running under.
+        * On other systems just returns null.
+        *
+        * This is used to advice the user that he should chgrp his config/data/images directory as the
+        * webserver user before he can install.
+        *
+        * Public because SqliteInstaller needs it, and doesn't subclass Installer.
+        *
+        * @return string
+        */
+       public static function maybeGetWebserverPrimaryGroup() {
+               if ( ! function_exists('posix_getegid') || ! function_exists('posix_getpwuid') ) {
+                       # I don't know this, this isn't UNIX
+                       return null;
+               }
+
+               # posix_getegid() *not* getmygid() because we want the group of the webserver,
+               # not whoever owns the current script
+               $gid = posix_getegid();
+               $getpwuid = posix_getpwuid( $gid );
+               $group = $getpwuid["name"];
+
+               return $group;
+       }
+}
diff --git a/includes/installer/InstallerDBType.php b/includes/installer/InstallerDBType.php
new file mode 100644 (file)
index 0000000..93cde56
--- /dev/null
@@ -0,0 +1,359 @@
+<?php
+
+/**
+ * Base class for DBMS-specific installation helper classes
+ */
+abstract class InstallerDBType {
+       /** The Installer object */
+       var $parent;
+
+       /* Database connection */
+       var $db;
+
+       /** Internal variables for installation */
+       protected $internalDefaults = array();
+
+       /** Array of MW configuration globals this class uses */
+       protected $globalNames = array();
+
+       /**
+        * Return the internal name, e.g. 'mysql', or 'sqlite'
+        */
+       abstract function getName();
+
+       /**
+        * @return true if the client library is compiled in
+        */
+       abstract function isCompiled();
+
+       /**
+        * Get an array of MW configuration globals that will be configured by this class.
+        */
+       public function getGlobalNames() {
+               return $this->globalNames;
+       }
+
+       /**
+        * Get HTML for a web form that configures this database. Configuration
+        * at this time should be the minimum needed to connect and test 
+        * whether install or upgrade is required.
+        *
+        * If this is called, $this->parent can be assumed to be a WebInstaller
+        */
+       abstract function getConnectForm();
+
+       /**
+        * Set variables based on the request array, assuming it was submitted
+        * via the form returned by getConnectForm(). Validate the connection 
+        * settings by attempting to connect with them.
+        *
+        * If this is called, $this->parent can be assumed to be a WebInstaller
+        *
+        * @return Status
+        */
+       abstract function submitConnectForm();
+
+       /**
+        * Get HTML for a web form that retrieves settings used for installation.
+        * $this->parent can be assumed to be a WebInstaller.
+        * If the DB type has no settings beyond those already configured with 
+        * getConnectForm(), this should return false.
+        */
+       abstract function getSettingsForm();
+
+       /**
+        * Set variables based on the request array, assuming it was submitted via
+        * the form return by getSettingsForm().
+        * @return Status
+        */
+       abstract function submitSettingsForm();
+
+       /**
+        * Connect to the database using the administrative user/password currently
+        * defined in the session. On success, return the connection, on failure, 
+        * return a Status object.
+        *
+        * This may be called multiple times, so the result should be cached.
+        */
+       abstract function getConnection();
+
+       /**
+        * Create the database and return a Status object indicating success or
+        * failure.
+        *
+        * @return Status
+        */
+       abstract function setupDatabase();
+
+       /**
+        * Create database tables from scratch
+        * @return \type Status
+        */
+       abstract function createTables();
+
+       /**
+        * Perform database upgrades
+        * @todo make abstract
+        */
+       /*abstract*/ function doUpgrade() {
+               return false;
+       }
+
+       /**
+        * Return any table options to be applied to all tables that don't
+        * override them
+        * @return Array
+        */
+       function getTableOptions() {
+               return array();
+       }
+
+       /**
+        * Get the DBMS-specific options for LocalSettings.php generation.
+        * @return String
+        */
+       abstract function getLocalSettings();
+
+       /** 
+        * Construct and initialise parent.
+        * This is typically only called from Installer::getDBInstaller()
+        */
+       function __construct( $parent ) {
+               $this->parent = $parent;
+       }
+
+       /**
+        * Convenience function
+        * Check if a named extension is present
+        */
+       function checkExtension( $name ) {
+               wfSuppressWarnings();
+               $compiled = extension_loaded( $name )
+                       || ( $this->parent->haveDl() && dl( $name . '.' . PHP_SHLIB_SUFFIX ) );
+               wfRestoreWarnings();
+               return $compiled;
+       }
+
+       /**
+        * Get the internationalised name for this DBMS
+        */
+       function getReadableName() {
+               return wfMsg( 'config-type-' . $this->getName() );
+       }
+       
+       /**
+        * Get a name=>value map of MW configuration globals that overrides
+        * DefaultSettings.php
+        */
+       function getGlobalDefaults() {
+               return array();
+       }
+
+       /**
+        * Get a name=>value map of internal variables used during installation
+        */
+       public function getInternalDefaults() {
+               return $this->internalDefaults;
+       }
+
+       /**
+        * Get a variable, taking local defaults into account
+        */
+       function getVar( $var, $default = null ) {
+               $defaults = $this->getGlobalDefaults();
+               $internal = $this->getInternalDefaults();
+               if ( isset( $defaults[$var] ) ) {
+                       $default = $defaults[$var];
+               } elseif ( isset( $internal[$var] ) ) {
+                       $default = $internal[$var];
+               }
+               return $this->parent->getVar( $var, $default );
+       }
+
+       /**
+        * Convenience alias for $this->parent->setVar()
+        */
+       function setVar( $name, $value ) {
+               $this->parent->setVar( $name, $value );
+       }
+
+       /**
+        * Get a labelled text box to configure a local variable
+        */
+       function getTextBox( $var, $label, $attribs = array() ) {
+               $name = $this->getName() . '_' . $var;
+               $value = $this->getVar( $var );
+               return $this->parent->getTextBox( array(
+                       'var' => $var,
+                       'label' => $label,
+                       'attribs' => $attribs,
+                       'controlName' => $name,
+                       'value' => $value
+               ) );
+       }
+
+       /**
+        * Get a labelled password box to configure a local variable
+        * Implements password hiding
+        */
+       function getPasswordBox( $var, $label, $attribs = array() ) {
+               $name = $this->getName() . '_' . $var;
+               $value = $this->getVar( $var );
+               return $this->parent->getPasswordBox( array(
+                       'var' => $var,
+                       'label' => $label,
+                       'attribs' => $attribs,
+                       'controlName' => $name,
+                       'value' => $value
+               ) );
+       }
+
+       /**
+        * Get a labelled checkbox to configure a local boolean variable
+        */
+       function getCheckBox( $var, $label, $attribs = array() ) {
+               $name = $this->getName() . '_' . $var;
+               $value = $this->getVar( $var );
+               return $this->parent->getCheckBox( array(
+                       'var' => $var,
+                       'label' => $label,
+                       'attribs' => $attribs,
+                       'controlName' => $name,
+                       'value' => $value,
+               ));
+       }
+
+       /**
+        * Get a set of labelled radio buttons
+        *
+        * @param array $params
+        *    Parameters are:
+        *      var:            The variable to be configured (required)
+        *      label:          The message name for the label (required)
+        *      itemLabelPrefix: The message name prefix for the item labels (required)
+        *      values:         List of allowed values (required)
+        *      itemAttribs     Array of attribute arrays, outer key is the value name (optional)
+        *
+        */
+       function getRadioSet( $params ) {
+               $params['controlName'] = $this->getName() . '_' . $params['var'];
+               $params['value'] = $this->getVar( $params['var'] );
+               return $this->parent->getRadioSet( $params );
+       }
+
+       /**
+        * Convenience function to set variables based on form data.
+        * Assumes that variables containing "password" in the name are (potentially
+        * fake) passwords.
+        * @param array $varNames
+        */
+       function setVarsFromRequest( $varNames ) {
+               return $this->parent->setVarsFromRequest( $varNames, $this->getName() . '_' );
+       }
+
+       /**
+        * Determine whether an existing installation of MediaWiki is present in 
+        * the configured administrative connection. Returns true if there is 
+        * such a wiki, false if the database doesn't exist.
+        *
+        * Traditionally, this is done by testing for the existence of either 
+        * the revision table or the cur table.
+        *
+        * @return boolean
+        */
+       function needsUpgrade() {
+               $status = $this->getConnection();
+               if ( !$status->isOK() ) {
+                       return false;
+               }
+               $conn = $status->value;
+               if ( !$conn->selectDB( $this->getVar( 'wgDBname' ) ) ) {
+                       return false;
+               }
+               return $conn->tableExists( 'cur' ) || $conn->tableExists( 'revision' );
+       }
+
+       /**
+        * Get a standard install-user fieldset
+        */
+       function getInstallUserBox() {
+               return
+                       Xml::openElement( 'fieldset' ) .
+                       Xml::element( 'legend', array(), wfMsg( 'config-db-install-account' ) ) .
+                       $this->getTextBox( '_InstallUser', 'config-db-username' ) .
+                       $this->getPasswordBox( '_InstallPassword', 'config-db-password' ) .
+                       $this->parent->getHelpBox( 'config-db-install-help' ) .
+                       Xml::closeElement( 'fieldset' );
+       }
+
+       /**
+        * Submit a standard install user fieldset
+        */
+       function submitInstallUserBox() {
+               $this->setVarsFromRequest( array( '_InstallUser', '_InstallPassword' ) );
+               return Status::newGood();
+       }
+
+       /**
+        * Get a standard web-user fieldset
+        * @param string $noCreateMsg Message to display instead of the creation checkbox. 
+        *   Set this to false to show a creation checkbox.
+        */
+       function getWebUserBox( $noCreateMsg = false ) {
+               $name = $this->getName();
+               $s = Xml::openElement( 'fieldset' ) .
+                       Xml::element( 'legend', array(), wfMsg( 'config-db-web-account' ) ) .
+                       $this->getCheckBox( 
+                               '_SameAccount', 'config-db-web-account-same',
+                               array( 'class' => 'hideShowRadio', 'rel' => 'dbOtherAccount' )
+                       ) .
+                       Xml::openElement( 'div', array( 'id' => 'dbOtherAccount', 'style' => 'display: none;' ) ) .
+                       $this->getTextBox( 'wgDBuser', 'config-db-username' ) .
+                       $this->getPasswordBox( 'wgDBpassword', 'config-db-password' ) .
+                       $this->parent->getHelpBox( 'config-db-web-help' );
+               if ( $noCreateMsg ) {
+                       $s .= $this->parent->getWarningBox( wfMsgNoTrans( $noCreateMsg ) );
+               } else {
+                       $s .= $this->getCheckBox( '_CreateDBAccount', 'config-db-web-create' );
+               }
+               $s .= Xml::closeElement( 'div' ) . Xml::closeElement( 'fieldset' );
+               return $s;
+       }
+
+       /**
+        * Submit the form from getWebUserBox().
+        * @return Status
+        */
+       function submitWebUserBox() {
+               $this->setVarsFromRequest( array( 'wgDBuser', 'wgDBpassword', 
+                       '_SameAccount', '_CreateDBAccount' ) );
+               if ( $this->getVar( '_SameAccount' ) ) {
+                       $this->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
+                       $this->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
+               }
+               return Status::newGood();
+       }
+
+       /**
+        * Common function for databases that don't understand the MySQLish syntax of interwiki.sql
+        */
+       protected function populateInterwikiTable( $db ) {
+               global $IP;
+               // Originally from DatabasePostgres
+               $f = fopen( "$IP/maintenance/interwiki.sql", 'r' );
+               if ( $f == false ) {
+                       return Status::newFatal( 'config-install-interwiki-sql' );
+               }
+               $table = $db->tableName( 'interwiki' );
+               $sql = "INSERT INTO $table(iw_prefix,iw_url,iw_local) VALUES ";
+               while ( !feof( $f ) ) {
+                       $line = fgets( $f, 1024 );
+                       $matches = array();
+                       if ( !preg_match( '/^\s*(\(.+?),(\d)\)/', $line, $matches ) ) continue;
+                       $db->query( "$sql $matches[1],$matches[2])" );
+               }
+               return Status::newGood();
+       }
+
+}
+
diff --git a/includes/installer/LocalSettingsGenerator.php b/includes/installer/LocalSettingsGenerator.php
new file mode 100644 (file)
index 0000000..5d8684f
--- /dev/null
@@ -0,0 +1,251 @@
+<?php
+
+class LocalSettingsGenerator {
+       private $extensions, $values = array();
+       private $configPath, $dbSettings = '';
+       private $safeMode = false;
+       private $installer;
+
+       /**
+        * Construtor.
+        * @param $installer Installer subclass
+        */
+       public function __construct( Installer $installer ) {
+               $this->installer = $installer;
+               $this->configPath = $installer->getVar( 'IP' ) . '/config';
+               $this->extensions = $installer->getVar( '_Extensions' );
+               $db = $installer->getDBInstaller( $installer->getVar( 'wgDBtype' ) );
+               
+               $confItems = array_merge( array( 'wgScriptPath', 'wgScriptExtension',
+                       'wgPasswordSender', 'wgImageMagickConvertCommand', 'wgShellLocale',
+                       'wgLanguageCode', 'wgEnableEmail', 'wgEnableUserEmail', 'wgDiff3',
+                       'wgEnotifUserTalk', 'wgEnotifWatchlist', 'wgEmailAuthentication',
+                       'wgDBtype', 'wgSecretKey', 'wgRightsUrl', 'wgSitename', 'wgRightsIcon',
+                       'wgRightsText', 'wgRightsCode', 'wgMainCacheType', 'wgEnableUploads',
+                       'wgMainCacheType', '_MemCachedServers', 'wgDBserver', 'wgDBuser',
+                       'wgDBpassword' ), $db->getGlobalNames() );
+               $boolItems = array( 'wgEnableEmail', 'wgEnableUserEmail', 'wgEnotifUserTalk',
+                       'wgEnotifWatchlist', 'wgEmailAuthentication', 'wgEnableUploads' );
+               foreach( $confItems as $c ) {
+                       $val = $installer->getVar( $c );
+                       if( in_array( $c, $boolItems ) ) {
+                               $val = wfBoolToStr( $val );
+                       }
+                       $this->values[$c] = $val;
+               }
+               $this->dbSettings = $db->getLocalSettings();
+               $this->safeMode = $installer->getVar( '_SafeMode' );
+               $this->values['wgEmergencyContact'] = $this->values['wgPasswordSender'];
+               $this->values = wfArrayMap( array( 'LocalSettingsGenerator', 'escapePhpString' ), $this->values );
+       }
+
+       public static function escapePhpString( $string ) {
+               if ( is_array( $string ) || is_object( $string ) ) {
+                       return false;
+               }
+               return strtr( $string,
+                       array(
+                               "\n" => "\\n",
+                               "\r" => "\\r",
+                               "\t" => "\\t",
+                               "\\" => "\\\\",
+                               "\$" => "\\\$",
+                               "\"" => "\\\""
+                       ));
+       }
+
+       /**
+        * Write the file
+        * @param $secretKey String A random string to
+        * @return boolean On successful file write
+        */
+       public function writeLocalSettings() {
+               $localSettings = $this->getDefaultText();
+               if( count( $this->extensions ) ) {
+                       $localSettings .= "\n# The following extensions were automatically enabled:\n";
+                       foreach( $this->extensions as $ext ) {
+                               $localSettings .= "require( 'extensions/$ext/$ext.php' );\n";
+                       }
+               }
+               wfSuppressWarnings();
+               $ret = file_put_contents( $this->configPath . '/LocalSettings.php', $localSettings );
+               wfRestoreWarnings();
+               if ( !$ret ) {
+                       $warn = wfMsg( 'config-install-localsettings-unwritable' ) . '
+<textarea name="LocalSettings" id="LocalSettings" cols="80" rows="25" readonly="readonly">'
+                               . htmlspecialchars( $localSettings ) . '</textarea>';
+                       $this->installer->output->addWarning( $warn );
+               }
+               return $ret;
+       }
+       
+       private function buildMemcachedServerList() {
+               $servers = $this->values['_MemCachedServers'];
+               if( !$servers ) {
+                       return 'array()';
+               } else {
+                       $ret = 'array( ';
+                       $servers = explode( ',', $servers );
+                       foreach( $servers as $srv ) {
+                               $srv = trim( $srv );
+                               $ret .= "'$srv', ";
+                       }
+                       return rtrim( $ret, ', ' ) . ' )';
+               }
+       }
+
+       private function getDefaultText() {
+               if( !$this->values['wgImageMagickConvertCommand'] ) {
+                       $this->values['wgImageMagickConvertCommand'] = '/usr/bin/convert';
+                       $magic = '#';
+               } else {
+                       $magic = '';
+               }
+               if( !$this->values['wgShellLocale'] ) {
+                       $this->values['wgShellLocale'] = 'en_US.UTF-8';
+                       $locale = '#';
+               } else {
+                       $locale = '';
+               }
+               $rights = $this->values['wgRightsUrl'] ? '' : '#';
+               $hashedUploads = $this->safeMode ? '#' : '';
+               switch( $this->values['wgMainCacheType'] ) {
+                       case 'anything':
+                       case 'db':
+                       case 'memcached':
+                       case 'accel':
+                               $cacheType = 'CACHE_' . strtoupper( $this->values['wgMainCacheType']);
+                               break;
+                       case 'none':
+                       default:
+                               $cacheType = 'CACHE_NONE';
+               }
+               $mcservers = $this->buildMemcachedServerList();
+               return "<?php
+# This file was automatically generated by the MediaWiki {$GLOBALS['wgVersion']}
+# installer. If you make manual changes, please keep track in case you
+# need to recreate them later.
+#
+# See includes/DefaultSettings.php for all configurable settings
+# and their default values, but don't forget to make changes in _this_
+# file, not there.
+#
+# Further documentation for configuration settings may be found at:
+# http://www.mediawiki.org/wiki/Manual:Configuration_settings
+
+# If you customize your file layout, set \$IP to the directory that contains
+# the other MediaWiki files. It will be used as a base to locate files.
+if( defined( 'MW_INSTALL_PATH' ) ) {
+       \$IP = MW_INSTALL_PATH;
+} else {
+       \$IP = dirname( __FILE__ );
+}
+
+\$path = array( \$IP, \"\$IP/includes\", \"\$IP/languages\" );
+set_include_path( implode( PATH_SEPARATOR, \$path ) . PATH_SEPARATOR . get_include_path() );
+
+require_once( \"\$IP/includes/DefaultSettings.php\" );
+
+if ( \$wgCommandLineMode ) {
+       if ( isset( \$_SERVER ) && array_key_exists( 'REQUEST_METHOD', \$_SERVER ) ) {
+               die( \"This script must be run from the command line\\n\" );
+       }
+}
+## Uncomment this to disable output compression
+# \$wgDisableOutputCompression = true;
+
+\$wgSitename         = \"{$this->values['wgSitename']}\";
+
+## The URL base path to the directory containing the wiki;
+## defaults for all runtime URL paths are based off of this.
+## For more information on customizing the URLs please see:
+## http://www.mediawiki.org/wiki/Manual:Short_URL
+\$wgScriptPath       = \"{$this->values['wgScriptPath']}\";
+\$wgScriptExtension  = \"{$this->values['wgScriptExtension']}\";
+
+## The relative URL path to the skins directory
+\$wgStylePath        = \"\$wgScriptPath/skins\";
+
+## The relative URL path to the logo.  Make sure you change this from the default,
+## or else you'll overwrite your logo when you upgrade!
+\$wgLogo             = \"\$wgStylePath/common/images/wiki.png\";
+
+## UPO means: this is also a user preference option
+
+\$wgEnableEmail      = {$this->values['wgEnableEmail']};
+\$wgEnableUserEmail  = {$this->values['wgEnableUserEmail']}; # UPO
+
+\$wgEmergencyContact = \"{$this->values['wgEmergencyContact']}\";
+\$wgPasswordSender = \"{$this->values['wgPasswordSender']}\";
+
+\$wgEnotifUserTalk = {$this->values['wgEnotifUserTalk']}; # UPO
+\$wgEnotifWatchlist = {$this->values['wgEnotifWatchlist']}; # UPO
+\$wgEmailAuthentication = {$this->values['wgEmailAuthentication']};
+
+## Database settings
+\$wgDBtype           = \"{$this->values['wgDBtype']}\";
+\$wgDBserver         = \"{$this->values['wgDBserver']}\";
+\$wgDBname           = \"{$this->values['wgDBname']}\";
+\$wgDBuser           = \"{$this->values['wgDBuser']}\";
+\$wgDBpassword       = \"{$this->values['wgDBpassword']}\";
+
+{$this->dbSettings}
+
+## Shared memory settings
+\$wgMainCacheType = $cacheType;
+\$wgMemCachedServers = $mcservers;
+
+## To enable image uploads, make sure the 'images' directory
+## is writable, then set this to true:
+\$wgEnableUploads       = {$this->values['wgEnableUploads']};
+{$magic}\$wgUseImageMagick = true;
+{$magic}\$wgImageMagickConvertCommand = \"{$this->values['wgImageMagickConvertCommand']}\";
+
+## If you use ImageMagick (or any other shell command) on a
+## Linux server, this will need to be set to the name of an
+## available UTF-8 locale
+{$locale}\$wgShellLocale = \"{$this->values['wgShellLocale']}\";
+
+## If you want to use image uploads under safe mode,
+## create the directories images/archive, images/thumb and
+## images/temp, and make them all writable. Then uncomment
+## this, if it's not already uncommented:
+{$hashedUploads}\$wgHashedUploadDirectory = false;
+
+## If you have the appropriate support software installed
+## you can enable inline LaTeX equations:
+\$wgUseTeX           = false;
+
+## Set \$wgCacheDirectory to a writable directory on the web server
+## to make your wiki go slightly faster. The directory should not
+## be publically accessible from the web.
+#\$wgCacheDirectory = \"\$IP/cache\";
+
+\$wgLocalInterwiki   = strtolower( \$wgSitename );
+
+\$wgLanguageCode = \"{$this->values['wgLanguageCode']}\";
+
+\$wgSecretKey = \"{$this->values['wgSecretKey']}\";
+
+## Default skin: you can change the default skin. Use the internal symbolic
+## names, ie 'standard', 'nostalgia', 'cologneblue', 'monobook':
+\$wgDefaultSkin = 'monobook';
+
+## For attaching licensing metadata to pages, and displaying an
+## appropriate copyright notice / icon. GNU Free Documentation
+## License and Creative Commons licenses are supported so far.
+{$rights}\$wgEnableCreativeCommonsRdf = true;
+\$wgRightsPage = \"\"; # Set to the title of a wiki page that describes your license/copyright
+\$wgRightsUrl = \"{$this->values['wgRightsUrl']}\";
+\$wgRightsText = \"{$this->values['wgRightsText']}\";
+\$wgRightsIcon = \"{$this->values['wgRightsIcon']}\";
+# \$wgRightsCode = \"{$this->values['wgRightsCode']}\"; # Not yet used
+
+\$wgDiff3 = \"{$this->values['wgDiff3']}\";
+
+# When you make changes to this configuration file, this will make
+# sure that cached pages are cleared.
+\$wgCacheEpoch = max( \$wgCacheEpoch, gmdate( 'YmdHis', @filemtime( __FILE__ ) ) );
+";
+       }
+}
\ No newline at end of file
diff --git a/includes/installer/MysqlInstaller.php b/includes/installer/MysqlInstaller.php
new file mode 100644 (file)
index 0000000..58c667d
--- /dev/null
@@ -0,0 +1,414 @@
+<?php
+
+class MysqlInstaller extends InstallerDBType {
+       protected $globalNames = array(
+               'wgDBserver',
+               'wgDBname',
+               'wgDBuser',
+               'wgDBpassword',
+               'wgDBprefix',
+               'wgDBTableOptions',
+               'wgDBmysql5',
+       );
+
+       protected $internalDefaults = array(
+               '_MysqlEngine' => 'InnoDB',
+               '_MysqlCharset' => 'binary',
+       );
+
+       var $supportedEngines = array( 'InnoDB', 'MyISAM' );
+
+       var $minimumVersion = '4.0.14';
+
+       var $webUserPrivs = array(
+               'DELETE',
+               'INSERT',
+               'SELECT',
+               'UPDATE',
+               'CREATE TEMPORARY TABLES',
+       );
+
+       function getName() {
+               return 'mysql';
+       }
+       
+       function isCompiled() {
+               return $this->checkExtension( 'mysql' );
+       }
+
+       function getGlobalDefaults() {
+               return array();
+       }
+
+       function getConnectForm() {
+               return
+                       $this->getTextBox( 'wgDBserver', 'config-db-host' ) .
+                       $this->parent->getHelpBox( 'config-db-host-help' ) . 
+                       Xml::openElement( 'fieldset' ) .
+                       Xml::element( 'legend', array(), wfMsg( 'config-db-wiki-settings' ) ) .
+                       $this->getTextBox( 'wgDBname', 'config-db-name' ) .
+                       $this->parent->getHelpBox( 'config-db-name-help' ) .
+                       $this->getTextBox( 'wgDBprefix', 'config-db-prefix' ) .
+                       $this->parent->getHelpBox( 'config-db-prefix-help' ) .
+                       Xml::closeElement( 'fieldset' ) .
+                       $this->getInstallUserBox();
+       }
+
+       function submitConnectForm() {
+               // Get variables from the request
+               $newValues = $this->setVarsFromRequest( array( 'wgDBserver', 'wgDBname', 'wgDBprefix' ) );
+
+               // Validate them
+               $status = Status::newGood();
+               if ( !strlen( $newValues['wgDBname'] ) ) {
+                       $status->fatal( 'config-missing-db-name' );
+               } elseif ( !preg_match( '/^[a-zA-Z0-9_]+$/', $newValues['wgDBname'] ) ) {
+                       $status->fatal( 'config-invalid-db-name', $newValues['wgDBname'] );
+               }
+               if ( !preg_match( '/^[a-zA-Z0-9_]*$/', $newValues['wgDBprefix'] ) ) {
+                       $status->fatal( 'config-invalid-db-prefix', $newValues['wgDBprefix'] );
+               }
+               if ( !$status->isOK() ) {
+                       return $status;
+               }
+
+               // Submit user box
+               $status = $this->submitInstallUserBox();
+               if ( !$status->isOK() ) {
+                       return $status;
+               }
+
+               // Try to connect
+               $status = $this->getConnection();
+               if ( !$status->isOK() ) {
+                       return $status;
+               }
+               $conn = $status->value;
+
+               // Check version
+               $version = $conn->getServerVersion();
+               if ( version_compare( $version, $this->minimumVersion ) < 0 ) {
+                       return Status::newFatal( 'config-mysql-old', $this->minimumVersion, $version );
+               }
+
+               return $status;
+       }
+
+       function getConnection() {
+               $status = Status::newGood();
+               try {
+                       $this->db = new DatabaseMysql(
+                               $this->getVar( 'wgDBserver' ),
+                               $this->getVar( '_InstallUser' ),
+                               $this->getVar( '_InstallPassword' ),
+                               false,
+                               false,
+                               0, 
+                               $this->getVar( 'wgDBprefix' )
+                       );
+                       $status->value = $this->db;
+                       return $status;
+               } catch ( DBConnectionError $e ) {
+                       $status->fatal( 'config-connection-error', $e->getMessage() );
+               }
+               return $status;
+       }
+
+       function doUpgrade() {
+               $status = $this->getConnection();
+               if ( !$status->isOK() ) {
+                       $this->parent->showStatusError( $status );
+                       return;
+               }
+               $conn = $status->value;
+
+               # Determine existing default character set
+               if ( $conn->tableExists( "revision" ) ) {
+                       $revision = $conn->escapeLike( $this->getVar( 'wgDBprefix' ) . 'revision' );
+                       $res = $conn->query( "SHOW TABLE STATUS LIKE '$revision'" );
+                       $row = $conn->fetchObject( $res );
+                       if ( !$row ) {
+                               $this->parent->showMessage( 'config-show-table-status' );
+                               $existingSchema = false;
+                               $existingEngine = false;
+                       } else {
+                               if ( preg_match( '/^latin1/', $row->Collation ) ) {
+                                       $existingSchema = 'mysql4';
+                               } elseif ( preg_match( '/^utf8/', $row->Collation ) ) {
+                                       $existingSchema = 'mysql5';
+                               } elseif ( preg_match( '/^binary/', $row->Collation ) ) {
+                                       $existingSchema = 'mysql5-binary';
+                               } else {
+                                       $existingSchema = false;
+                                       $this->parent->showMessage( 'config-unknown-collation' );
+                               }
+                               if ( isset( $row->Engine ) ) {
+                                       $existingEngine = $row->Engine;
+                               } else {
+                                       $existingEngine = $row->Type;
+                               }
+                       }
+               }
+               
+               // TODO
+       }
+
+       /**
+        * Get a list of storage engines that are available and supported
+        */
+       function getEngines() {
+               $engines = array( 'InnoDB', 'MyISAM' );
+               $status = $this->getConnection();
+               if ( !$status->isOK() ) {
+                       return $engines;
+               }
+               $conn = $status->value;
+
+               $version = $conn->getServerVersion();
+               if ( version_compare( $version, "4.1.2", "<" ) ) {
+                       // No SHOW ENGINES in this version
+                       return $engines;
+               }
+
+               $engines = array();
+               $res = $conn->query( 'SHOW ENGINES' );
+               foreach ( $res as $row ) {
+                       if ( $row->Support == 'YES' || $row->Support == 'DEFAULT' ) {
+                               $engines[] = $row->Engine;
+                       }
+               }
+               $engines = array_intersect( $this->supportedEngines, $engines );
+               return $engines;
+       }
+
+       /**
+        * Get a list of character sets that are available and supported
+        */
+       function getCharsets() {
+               $status = $this->getConnection();
+               $mysql5 = array( 'binary', 'utf8' );
+               $mysql4 = array( 'mysql4' );
+               if ( !$status->isOK() ) {
+                       return $mysql5;
+               }
+               if ( version_compare( $status->value->getServerVersion(), '4.1.0', '>=' ) ) {
+                       return $mysql5;
+               }
+               return $mysql4;
+       }
+
+       /**
+        * Return true if the install user can create accounts
+        */
+       function canCreateAccounts() {
+               $status = $this->getConnection();
+               if ( !$status->isOK() ) {
+                       return false;
+               }
+               $conn = $status->value;
+
+               // Check version, need INFORMATION_SCHEMA and CREATE USER
+               if ( version_compare( $conn->getServerVersion(), '5.0.2', '<' ) ) {
+                       return false;
+               }
+
+               // Get current account name
+               $currentName = $conn->selectField( '', 'CURRENT_USER()', '', __METHOD__ );
+               $parts = explode( '@', $currentName );
+               if ( count( $parts ) != 2 ) {
+                       return false;
+               }
+               $quotedUser = $conn->addQuotes( $parts[0] ) . 
+                       '@' . $conn->addQuotes( $parts[1] );
+
+               // The user needs to have INSERT on mysql.* to be able to CREATE USER
+               // The grantee will be double-quoted in this query, as required
+               $res = $conn->select( 'INFORMATION_SCHEMA.USER_PRIVILEGES', '*', 
+                       array( 'GRANTEE' => $quotedUser ), __METHOD__ );
+               $insertMysql = false;
+               $grantOptions = array_flip( $this->webUserPrivs );
+               foreach ( $res as $row ) {
+                       if ( $row->PRIVILEGE_TYPE == 'INSERT' ) {
+                               $insertMysql = true;
+                       }
+                       if ( $row->IS_GRANTABLE ) {
+                               unset( $grantOptions[$row->PRIVILEGE_TYPE] );
+                       }
+               }
+
+               // Check for DB-specific privs for mysql.*
+               if ( !$insertMysql ) {
+                       $row = $conn->selectRow( 'INFORMATION_SCHEMA.SCHEMA_PRIVILEGES', '*',
+                               array( 
+                                       'GRANTEE' => $quotedUser,
+                                       'TABLE_SCHEMA' => 'mysql',
+                                       'PRIVILEGE_TYPE' => 'INSERT',
+                               ), __METHOD__ );
+                       if ( $row ) {
+                               $insertMysql = true;
+                       }
+               }
+
+               if ( !$insertMysql ) {
+                       return false;
+               }
+
+               // Check for DB-level grant options
+               $res = $conn->select( 'INFORMATION_SCHEMA.SCHEMA_PRIVILEGES', '*', 
+                       array(
+                               'GRANTEE' => $quotedUser,
+                               'IS_GRANTABLE' => 1,
+                       ), __METHOD__ );
+               foreach ( $res as $row ) {
+                       $regex = $conn->likeToRegex( $row->TABLE_SCHEMA );
+                       if ( preg_match( $regex, $this->getVar( 'wgDBname' ) ) ) {
+                               unset( $grantOptions[$row->PRIVILEGE_TYPE] );
+                       }
+               }
+               if ( count( $grantOptions ) ) {
+                       // Can't grant everything
+                       return false;
+               }
+               return true;
+       }
+
+       function getSettingsForm() {
+               if ( $this->canCreateAccounts() ) {
+                       $noCreateMsg = false;
+               } else {
+                       $noCreateMsg = 'config-db-web-no-create-privs';
+               }
+               $s = $this->getWebUserBox( $noCreateMsg );
+
+               // Do engine selector
+               $engines = $this->getEngines();
+               // If the current default engine is not supported, use an engine that is
+               if ( !in_array( $this->getVar( '_MysqlEngine' ), $engines ) ) {
+                       $this->setVar( '_MysqlEngine', reset( $engines ) );
+               }
+               if ( count( $engines ) >= 2 ) {
+                       $s .= $this->getRadioSet( array(
+                               'var' => '_MysqlEngine', 
+                               'label' => 'config-mysql-engine', 
+                               'itemLabelPrefix' => 'config-mysql-', 
+                               'values' => $engines
+                       ));
+                       $s .= $this->parent->getHelpBox( 'config-mysql-engine-help' );
+               }
+
+               // If the current default charset is not supported, use a charset that is
+               $charsets = $this->getCharsets();
+               if ( !in_array( $this->getVar( '_MysqlCharset' ), $charsets ) ) {
+                       $this->setVar( '_MysqlCharset', reset( $charsets ) );
+               }
+
+               // Do charset selector
+               if ( count( $charsets ) >= 2 ) {
+                       $s .= $this->getRadioSet( array(
+                               'var' => '_MysqlCharset',
+                               'label' => 'config-mysql-charset',
+                               'itemLabelPrefix' => 'config-mysql-',
+                               'values' => $charsets
+                       ));
+                       $s .= $this->parent->getHelpBox( 'config-mysql-charset-help' );
+               }
+
+               return $s;
+       }
+
+       function submitSettingsForm() {
+               $newValues = $this->setVarsFromRequest( array( '_MysqlEngine', '_MysqlCharset' ) );
+               $status = $this->submitWebUserBox();
+               if ( !$status->isOK() ) {
+                       return $status;
+               }
+
+               // Validate the create checkbox
+               $canCreate = $this->canCreateAccounts();
+               if ( !$canCreate ) {
+                       $this->setVar( '_CreateDBAccount', false );
+                       $create = false;
+               } else {
+                       $create = $this->getVar( '_CreateDBAccount' );
+               }
+
+               if ( !$create ) {
+                       // Test the web account
+                       try {
+                               $webConn = new Database( 
+                                       $this->getVar( 'wgDBserver' ),
+                                       $this->getVar( 'wgDBuser' ),
+                                       $this->getVar( 'wgDBpassword' ),
+                                       false,
+                                       false,
+                                       0, 
+                                       $this->getVar( 'wgDBprefix' )
+                               );
+                       } catch ( DBConnectionError $e ) {
+                               return Status::newFatal( 'config-connection-error', $e->getMessage() );
+                       }
+               }
+
+               // Validate engines and charsets
+               // This is done pre-submit already so it's just for security
+               $engines = $this->getEngines();
+               if ( !in_array( $this->getVar( '_MysqlEngine' ), $engines ) ) {
+                       $this->setVar( '_MysqlEngine', reset( $engines ) );
+               }
+               $charsets = $this->getCharsets();
+               if ( !in_array( $this->getVar( '_MysqlCharset' ), $charsets ) ) {
+                       $this->setVar( '_MysqlCharset', reset( $charsets ) );
+               }
+               return Status::newGood();
+       }
+
+       function setupDatabase() {
+               $status = $this->getConnection();
+               if ( !$status->isOK() ) {
+                       return $status;
+               }
+               $conn = $status->value;
+               $dbName = $this->getVar( 'wgDBname' );
+               if( !$conn->selectDB( $dbName ) ) {
+                       $conn->query( "CREATE DATABASE `$dbName`" );
+                       $conn->selectDB( $dbName );
+               }
+               return $status;
+       }
+
+       function createTables() {
+               global $IP;
+               $status = $this->getConnection();
+               if ( !$status->isOK() ) {
+                       return $status;
+               }
+               $this->db->selectDB( $this->getVar( 'wgDBname' ) );
+               if ( !$this->db->sourceFile( "$IP/maintenance/tables.sql" )
+                       || !$this->db->sourceFile( "$IP/maintenance/interwiki.sql" ) )
+               {
+                       //@todo
+               }
+               return Status::newGood();
+       }
+
+       function getTableOptions() {
+               return array( 'engine' => $this->getVar( '_MysqlEngine' ),
+                       'default charset' => $this->getVar( '_MysqlCharset' ) );
+       }
+
+       function getLocalSettings() {
+               $dbmysql5 = wfBoolToStr( $this->getVar( 'wgDBmysql5', true ) );
+               $prefix = $this->getVar( 'wgDBprefix' );
+               $opts = $this->getTableOptions();
+               $tblOpts = "ENGINE=" . $opts['engine'] . ', DEFAULT CHARSET=' . $opts['default charset'];
+               return
+"# MySQL specific settings
+\$wgDBprefix         = \"{$prefix}\";
+
+# MySQL table options to use during installation or update
+\$wgDBTableOptions   = \"{$tblOpts}\";
+
+# Experimental charset support for MySQL 4.1/5.0.
+\$wgDBmysql5 = {$dbmysql5};";
+       }
+}
diff --git a/includes/installer/OracleInstaller.php b/includes/installer/OracleInstaller.php
new file mode 100644 (file)
index 0000000..a97e1d9
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+
+class OracleInstaller extends InstallerDBType {
+
+       protected $globalNames = array(
+               'wgDBport',
+               'wgDBname',
+               'wgDBuser',
+               'wgDBpassword',
+               'wgDBprefix',
+       );
+
+       protected $internalDefaults = array(
+               '_InstallUser' => 'sys',
+               '_InstallPassword' => '',
+       );
+
+       function getName() {
+               return 'oracle';
+       }
+
+       function isCompiled() {
+               return $this->checkExtension( 'oci8' );
+       }
+
+       function getConnectForm() {
+               return
+                       Xml::openElement( 'fieldset' ) .
+                       Xml::element( 'legend', array(), wfMsg( 'config-db-wiki-settings' ) ) .
+                       $this->getTextBox( 'wgDBname', 'config-db-name' ) .
+                       $this->parent->getHelpBox( 'config-db-name-help' ) .
+                       $this->getTextBox( 'wgDBprefix', 'config-db-prefix' ) .
+                       $this->parent->getHelpBox( 'config-db-prefix-help' ) .
+                       Xml::closeElement( 'fieldset' ) .
+                       $this->getInstallUserBox();
+       }
+
+       function submitConnectForm() {
+               // Get variables from the request
+               $newValues = $this->setVarsFromRequest( array( 'wgDBname', 'wgDBprefix' ) );
+
+               // Validate them
+               $status = Status::newGood();
+               if ( !strlen( $newValues['wgDBname'] ) ) {
+                       $status->fatal( 'config-missing-db-name' );
+               } elseif ( !preg_match( '/^[a-zA-Z0-9_]+$/', $newValues['wgDBname'] ) ) {
+                       $status->fatal( 'config-invalid-db-name', $newValues['wgDBname'] );
+               }
+               if ( !preg_match( '/^[a-zA-Z0-9_]*$/', $newValues['wgDBprefix'] ) ) {
+                       $status->fatal( 'config-invalid-schema', $newValues['wgDBprefix'] );
+               }
+
+               // Submit user box
+               if ( $status->isOK() ) {
+                       $status->merge( $this->submitInstallUserBox() );
+               }
+               if ( !$status->isOK() ) {
+                       return $status;
+               }
+
+               // Try to connect
+               if ( $status->isOK() ) {
+                       $status->merge( $this->attemptConnection() );
+               }
+               if ( !$status->isOK() ) {
+                       return $status;
+               }
+
+               // Check version
+/*
+               $version = $this->conn->getServerVersion();
+               if ( version_compare( $version, $this->minimumVersion ) < 0 ) {
+                       return Status::newFatal( 'config-postgres-old', $this->minimumVersion, $version );
+               }
+*/
+               return $status;
+       }
+
+
+       function getSettingsForm() {}
+       
+       function submitSettingsForm() {}
+
+       function getConnection() {}
+
+       function setupDatabase() {}
+
+       function createTables() {}
+
+       function getLocalSettings() {
+               $prefix = $this->getVar( 'wgDBprefix' );
+               return
+"# Oracle specific settings
+\$wgDBprefix         = \"{$prefix}\";";
+       }
+}
diff --git a/includes/installer/PostgresInstaller.php b/includes/installer/PostgresInstaller.php
new file mode 100644 (file)
index 0000000..d384102
--- /dev/null
@@ -0,0 +1,135 @@
+<?php
+
+class PostgresInstaller extends InstallerDBType {
+
+       protected $globalNames = array(
+               'wgDBserver',
+               'wgDBport',
+               'wgDBname',
+               'wgDBuser',
+               'wgDBpassword',
+               'wgDBmwschema',
+               'wgDBts2schema',
+       );
+
+       protected $internalDefaults = array(
+               '_InstallUser' => 'postgres',
+               '_InstallPassword' => '',
+       );
+
+       var $minimumVersion = '8.1';
+
+       var $conn;
+
+       function getName() {
+               return 'postgres';
+       }
+
+       function isCompiled() {
+               return $this->checkExtension( 'pgsql' );
+       }
+
+       function getConnectForm() {
+               return
+                       $this->getTextBox( 'wgDBserver', 'config-db-host' ) .
+                       $this->parent->getHelpBox( 'config-db-host-help' ) . 
+                       $this->getTextBox( 'wgDBport', 'config-db-port' ) .
+                       Xml::openElement( 'fieldset' ) .
+                       Xml::element( 'legend', array(), wfMsg( 'config-db-wiki-settings' ) ) .
+                       $this->getTextBox( 'wgDBname', 'config-db-name' ) .
+                       $this->parent->getHelpBox( 'config-db-name-help' ) .
+                       $this->getTextBox( 'wgDBmwschema', 'config-db-schema' ) .
+                       $this->getTextBox( 'wgDBts2schema', 'config-db-ts2-schema' ) .
+                       $this->parent->getHelpBox( 'config-db-schema-help' ) .
+                       Xml::closeElement( 'fieldset' ) .
+                       $this->getInstallUserBox();
+       }
+
+       function submitConnectForm() {
+               // Get variables from the request
+               $newValues = $this->setVarsFromRequest( array( 'wgDBserver', 'wgDBport', 
+                       'wgDBname', 'wgDBmwschema', 'wgDBts2schema' ) );
+
+               // Validate them
+               $status = Status::newGood();
+               if ( !strlen( $newValues['wgDBname'] ) ) {
+                       $status->fatal( 'config-missing-db-name' );
+               } elseif ( !preg_match( '/^[a-zA-Z0-9_]+$/', $newValues['wgDBname'] ) ) {
+                       $status->fatal( 'config-invalid-db-name', $newValues['wgDBname'] );
+               }
+               if ( !preg_match( '/^[a-zA-Z0-9_]*$/', $newValues['wgDBmwschema'] ) ) {
+                       $status->fatal( 'config-invalid-schema', $newValues['wgDBmwschema'] );
+               }
+               if ( !preg_match( '/^[a-zA-Z0-9_]*$/', $newValues['wgDBts2schema'] ) ) {
+                       $status->fatal( 'config-invalid-ts2schema', $newValues['wgDBts2schema'] );
+               }
+
+               // Submit user box
+               if ( $status->isOK() ) {
+                       $status->merge( $this->submitInstallUserBox() );
+               }
+               if ( !$status->isOK() ) {
+                       return $status;
+               }
+
+               // Try to connect
+               if ( $status->isOK() ) {
+                       $status->merge( $this->attemptConnection() );
+               }
+               if ( !$status->isOK() ) {
+                       return $status;
+               }
+
+               // Check version
+               $version = $this->conn->getServerVersion();
+               if ( version_compare( $version, $this->minimumVersion ) < 0 ) {
+                       return Status::newFatal( 'config-postgres-old', $this->minimumVersion, $version );
+               }
+               return $status;
+       }
+
+       function attemptConnection() {
+               $status = Status::newGood();
+
+               try {
+                       $this->conn = new DatabasePostgres( 
+                               $this->getVar( 'wgDBserver' ),
+                               $this->getVar( '_InstallUser' ),
+                               $this->getVar( '_InstallPassword' ),
+                               'postgres' );
+                       $status->value = $this->conn;
+               } catch ( DBConnectionError $e ) {
+                       $status->fatal( 'config-connection-error', $e->getMessage() );
+               }
+               return $status;
+       }
+
+       function getConnection() {
+               return $this->attemptConnection();
+       }
+
+       function getSettingsForm() {
+               return false;
+       }
+
+       function submitSettingsForm() {
+               return Status::newGood();
+       }
+
+       function setupDatabase() {
+       }
+
+       function createTables() {
+       }
+
+       function getLocalSettings() {
+               $port = $this->getVar( 'wgDBport' );
+               $schema = $this->getVar( 'wgDBmwschema' );
+               $ts2 = $this->getVar( 'wgDBts2schema' );
+               return
+"# Postgres specific settings
+\$wgDBport           = \"{$port}\";
+\$wgDBmwschema       = \"{$schema}\";
+\$wgDBts2schema      = \"{$ts2}\";";
+       }
+}
diff --git a/includes/installer/SqliteInstaller.php b/includes/installer/SqliteInstaller.php
new file mode 100644 (file)
index 0000000..1d321e0
--- /dev/null
@@ -0,0 +1,186 @@
+<?php
+
+class SqliteInstaller extends InstallerDBType {
+       protected $globalNames = array(
+               'wgDBname',
+               'wgSQLiteDataDir',
+       );
+
+       function getName() {
+               return 'sqlite';
+       }
+
+       function isCompiled() {
+               return $this->checkExtension( 'pdo_sqlite' );
+       }
+
+       function getGlobalDefaults() {
+               if ( isset( $_SERVER['DOCUMENT_ROOT'] ) ) {
+                       $path = str_replace( 
+                               array( '/', '\\' ), 
+                               DIRECTORY_SEPARATOR, 
+                               dirname( $_SERVER['DOCUMENT_ROOT'] ) . '/data' 
+                       );
+                       return array( 'wgSQLiteDataDir' => $path );
+               } else {
+                       return array();
+               }
+       }
+
+       function getConnectForm() {
+               return $this->getTextBox( 'wgSQLiteDataDir', 'config-sqlite-dir' ) .
+                       $this->parent->getHelpBox( 'config-sqlite-dir-help' ) .
+                       $this->getTextBox( 'wgDBname', 'config-db-name' ) .
+                       $this->parent->getHelpBox( 'config-sqlite-name-help' );
+       }
+
+       function submitConnectForm() {
+               global $wgSQLiteDataDir;
+               $this->setVarsFromRequest( array( 'wgSQLiteDataDir', 'wgDBname' ) );
+
+               $dir = realpath( $this->getVar( 'wgSQLiteDataDir' ) );
+               if ( !$dir ) {
+                       // realpath() sometimes fails, especially on Windows
+                       $dir = $this->getVar( 'wgSQLiteDataDir' );
+               }
+               $this->setVar( 'wgSQLiteDataDir', $dir );
+               return self::dataDirOKmaybeCreate( $dir, true /* create? */ );
+       }
+
+       private static function dataDirOKmaybeCreate( $dir, $create = false ) {
+               if ( !is_dir( $dir ) ) {
+                       if ( !is_writable( dirname( $dir ) ) ) {
+                               $webserverGroup = Installer::maybeGetWebserverPrimaryGroup();
+                               if ( $webserverGroup !== null ) {
+                                       return Status::newFatal( 'config-sqlite-parent-unwritable-group', $dir, dirname( $dir ), basename( $dir ), $webserverGroup );
+                               } else {
+                                       return Status::newFatal( 'config-sqlite-parent-unwritable-nogroup', $dir, dirname( $dir ), basename( $dir ) );
+                               }
+                       }
+
+                       # Called early on in the installer, later we just want to sanity check
+                       # if it's still writable
+                       if ( $create ) {
+                               wfSuppressWarnings();
+                               $ok = wfMkdirParents( $dir, 0700 );
+                               wfRestoreWarnings();
+                               if ( !$ok ) {
+                                       return Status::newFatal( 'config-sqlite-mkdir-error', $dir );
+                               }
+                               # Put a .htaccess file in in case the user didn't take our advice
+                               file_put_contents( "$dir/.htaccess", "Deny from all\n" );
+                       }
+               }
+               if ( !is_writable( $dir ) ) {
+                       return Status::newFatal( 'config-sqlite-dir-unwritable', $dir );
+               }
+
+               # We haven't blown up yet, fall through
+               return Status::newGood();
+       }
+
+       function getConnection() {
+               global $wgSQLiteDataDir;
+
+               $status = Status::newGood();
+               $dir = $this->getVar( 'wgSQLiteDataDir' );
+               $dbName = $this->getVar( 'wgDBname' );
+
+               try {
+                       # FIXME: need more sensible constructor parameters, e.g. single associative array
+                       # Setting globals kind of sucks
+                       $wgSQLiteDataDir = $dir;
+                       $this->db = new DatabaseSqlite( false, false, false, $dbName );
+                       $status->value = $this->db;
+               } catch ( DBConnectionError $e ) {
+                       $status->fatal( 'config-sqlite-connection-error', $e->getMessage() );
+               }
+               return $status;
+       }
+
+       function needsUpgrade() {
+               $dir = $this->getVar( 'wgSQLiteDataDir' );
+               $dbName = $this->getVar( 'wgDBname' );
+               // Don't create the data file yet
+               if ( !file_exists( DatabaseSqlite::generateFileName( $dir, $dbName ) ) ) {
+                       return false;
+               }
+
+               // If the data file exists, look inside it
+               return parent::needsUpgrade();
+       }
+
+       function getSettingsForm() {
+               return false;
+       }
+
+       function submitSettingsForm() {
+               return Status::newGood();
+       }
+
+       function setupDatabase() {
+               $dir = $this->getVar( 'wgSQLiteDataDir' );
+
+               # Sanity check. We checked this before but maybe someone deleted the
+               # data dir between then and now
+               $dir_status = self::dataDirOKmaybeCreate( $dir, false /* create? */ );
+               if ( !$dir_status->isOK() ) {
+                       return $dir_status;
+               }
+
+               $db = $this->getVar( 'wgDBname' );
+               $file = DatabaseSqlite::generateFileName( $dir, $db );
+               if ( file_exists( $file ) ) {
+                       if ( !is_writable( $file ) ) {
+                               return Status::newFatal( 'config-sqlite-readonly', $file );
+                       }
+               } else {
+                       if ( file_put_contents( $file, '' ) === false ) {
+                               return Status::newFatal( 'config-sqlite-cant-create-db', $file );
+                       }
+               }
+               // nuke the unused settings for clarity
+               $this->setVar( 'wgDBserver', '' );
+               $this->setVar( 'wgDBuser', '' );
+               $this->setVar( 'wgDBpassword', '' );
+               return $this->getConnection();
+       }
+
+       function createTables() {
+               global $IP;
+               $status = $this->getConnection();
+               if ( !$status->isOK() ) {
+                       return $status;
+               }
+               // Process common MySQL/SQLite table definitions
+               $err = $this->db->sourceFile( "$IP/maintenance/tables.sql" );
+               if ( $err !== true ) {
+                       //@todo or...?
+                       $this->db->reportQueryError( $err, 0, $sql, __FUNCTION__ );
+               }
+               //@todo set up searchindex
+               // Create default interwikis
+               return $this->populateInterwikiTable( $this->db );
+       }
+
+       function doUpgrade() {
+               global $wgDatabase;
+               LBFactory::enableBackend();
+               $wgDatabase = wfGetDB( DB_MASTER );
+               ob_start( array( 'SqliteInstaller', 'outputHandler' ) );
+               do_all_updates( false, true );
+               ob_end_flush();
+               return true;
+       }
+
+       static function outputHandler( $string ) {
+               return htmlspecialchars( $string );
+       }
+
+       function getLocalSettings() {
+               $dir = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgSQLiteDataDir' ) );
+               return
+"# SQLite-specific settings
+\$wgSQLiteDataDir    = \"{$dir}\";";
+       }
+}
diff --git a/includes/installer/WebInstaller.php b/includes/installer/WebInstaller.php
new file mode 100644 (file)
index 0000000..d3625d3
--- /dev/null
@@ -0,0 +1,1721 @@
+<?php
+
+class WebInstaller extends Installer {
+       /** WebRequest object */
+       var $request;
+
+       /** Cached session array */
+       var $session;
+
+       /** Captured PHP error text. Temporary.
+        */
+       var $phpErrors;
+
+       /**
+        * The main sequence of page names. These will be displayed in turn.
+        * To add one:
+        *    * Add it here
+        *    * Add a config-page-<name> message
+        *    * Add a WebInstaller_<name> class
+        */
+       var $pageSequence = array(
+               'Language',
+               'Welcome',
+               'DBConnect',
+               'Upgrade',
+               'DBSettings',
+               'Name',
+               'Options',
+               'Install',
+               'Complete',
+       );
+
+       /**
+        * Out of sequence pages, selectable by the user at any time
+        */
+       var $otherPages = array(
+               'Restart',
+               'Readme',
+               'ReleaseNotes',
+               'Copying',
+               'UpgradeDoc', // Can't use Upgrade due to Upgrade step
+       );
+
+       /**
+        * Array of pages which have declared that they have been submitted, have validated
+        * their input, and need no further processing
+        */
+       var $happyPages;
+
+       /**
+        * List of "skipped" pages. These are pages that will automatically continue
+        * to the next page on any GET request. To avoid breaking the "back" button,
+        * they need to be skipped during a back operation.
+        */
+       var $skippedPages;
+
+       /**
+        * Flag indicating that session data may have been lost
+        */
+       var $showSessionWarning = false;
+
+       var $helpId = 0;
+       var $tabIndex = 1;
+
+       var $currentPageName;
+
+       /** Constructor */
+       function __construct( $request ) {
+               parent::__construct();
+               $this->output = new WebInstallerOutput( $this );
+               $this->request = $request;
+       }
+
+       /**
+        * Main entry point.
+        * @param array $session Initial session array
+        * @return array New session array
+        */
+       function execute( $session ) {
+               $this->session = $session;
+               if ( isset( $session['settings'] ) ) {
+                       $this->settings = $session['settings'] + $this->settings;
+               }
+               $this->exportVars();
+               $this->setupLanguage();
+
+               if ( isset( $session['happyPages'] ) ) {
+                       $this->happyPages = $session['happyPages'];
+               } else {
+                       $this->happyPages = array();
+               }
+               if ( isset( $session['skippedPages'] ) ) {
+                       $this->skippedPages = $session['skippedPages'];
+               } else {
+                       $this->skippedPages = array();
+               }
+               $lowestUnhappy = $this->getLowestUnhappy();
+
+               # Special case for Creative Commons partner chooser box
+               if ( $this->request->getVal( 'SubmitCC' ) ) {
+                       $page = $this->getPageByName( 'Options' );
+                       $this->output->useShortHeader();
+                       $page->submitCC();
+                       return $this->finish();
+               }
+               if ( $this->request->getVal( 'ShowCC' ) ) {
+                       $page = $this->getPageByName( 'Options' );
+                       $this->output->useShortHeader();
+                       $this->output->addHTML( $page->getCCDoneBox() );
+                       return $this->finish();
+               }
+
+               # Get the page name
+               $pageName = $this->request->getVal( 'page' );
+
+               if ( in_array( $pageName, $this->otherPages ) ) {
+                       # Out of sequence
+                       $pageId = false;
+                       $page = $this->getPageByName( $pageName );
+               } else {
+                       # Main sequence
+                       if ( !$pageName || !in_array( $pageName, $this->pageSequence ) ) {
+                               $pageId = $lowestUnhappy;
+                       } else {
+                               $pageId = array_search( $pageName, $this->pageSequence );
+                       }
+
+                       # If necessary, move back to the lowest-numbered unhappy page
+                       if ( $pageId > $lowestUnhappy ) {
+                               $pageId = $lowestUnhappy;
+                               if ( $lowestUnhappy == 0 ) {
+                                       # Knocked back to start, possible loss of session data
+                                       $this->showSessionWarning = true;
+                               }
+                       }
+                       $pageName = $this->pageSequence[$pageId];
+                       $page = $this->getPageByName( $pageName );
+               }
+
+               # If a back button was submitted, go back without submitting the form data
+               if ( $this->request->wasPosted() && $this->request->getBool( 'submit-back' ) ) {
+                       if ( $this->request->getVal( 'lastPage' ) ) {
+                               $nextPage = $this->request->getVal( 'lastPage' );
+                       } elseif ( $pageId !== false ) {
+                               # Main sequence page
+                               # Skip the skipped pages
+                               $nextPageId = $pageId;
+                               do {
+                                       $nextPageId--;
+                                       $nextPage = $this->pageSequence[$nextPageId];
+                               } while( isset( $this->skippedPages[$nextPage] ) );
+                       } else {
+                               $nextPage = $this->pageSequence[$lowestUnhappy];
+                       }
+                       $this->output->redirect( $this->getUrl( array( 'page' => $nextPage ) ) );
+                       return $this->finish();
+               }
+
+               # Execute the page
+               $this->currentPageName = $page->getName();
+               $this->startPageWrapper( $pageName );
+               $result = $page->execute();
+               $this->endPageWrapper();
+
+               if ( $result == 'skip' ) {
+                       # Page skipped without explicit submission
+                       # Skip it when we click "back" so that we don't just go forward again
+                       $this->skippedPages[$pageName] = true;
+                       $result = 'continue';
+               } else {
+                       unset( $this->skippedPages[$pageName] );
+               }
+
+               # If it was posted, the page can request a continue to the next page
+               if ( $result === 'continue' && !$this->output->headerDone() ) {
+                       if ( $pageId !== false ) {
+                               $this->happyPages[$pageId] = true;
+                       }
+                       $lowestUnhappy = $this->getLowestUnhappy();
+
+                       if ( $this->request->getVal( 'lastPage' ) ) {
+                               $nextPage = $this->request->getVal( 'lastPage' );
+                       } elseif ( $pageId !== false ) {
+                               $nextPage = $this->pageSequence[$pageId + 1];
+                       } else {
+                               $nextPage = $this->pageSequence[$lowestUnhappy];
+                       }
+                       if ( array_search( $nextPage, $this->pageSequence ) > $lowestUnhappy ) {
+                               $nextPage = $this->pageSequence[$lowestUnhappy];
+                       }
+                       $this->output->redirect( $this->getUrl( array( 'page' => $nextPage ) ) );
+               }
+               return $this->finish();
+       }
+
+       function getLowestUnhappy() {
+               if ( count( $this->happyPages ) == 0 ) {
+                       return 0;
+               } else {
+                       return max( array_keys( $this->happyPages ) ) + 1;
+               }
+       }
+
+       /**
+        * Start the PHP session. This may be called before execute() to start the PHP session.
+        */
+       function startSession() {
+               $sessPath = $this->getSessionSavePath();
+               if( $sessPath != '' ) {
+                       if( !is_dir( $sessPath ) || !is_writeable( $sessPath ) ) {
+                               $this->showError( 'config-session-path-bad', $sessPath );
+                               return false;
+                       }
+               } else {
+                       // If the path is unset it'll default to some system bit, which *probably* is ok...
+                       // not sure how to actually get what will be used.
+               }
+               if( wfIniGetBool( 'session.auto_start' ) || session_id() ) {
+                       // Done already
+                       return true;
+               }
+
+               $this->phpErrors = array();
+               set_error_handler( array( $this, 'errorHandler' ) );
+               session_start();
+               restore_error_handler();
+               if ( $this->phpErrors ) {
+                       $this->showError( 'config-session-error', $this->phpErrors[0] );
+                       return false;
+               }
+               return true;
+       }
+
+       /**
+        * Get the value of session.save_path
+        *
+        * Per http://www.php.net/manual/en/ref.session.php#ini.session.save-path,
+        * this might have some additional preceding parts which need to be
+        * ditched
+        *
+        * @return string
+        */
+       private function getSessionSavePath() {
+               $path = ini_get( 'session.save_path' );
+               $path = ltrim( substr( $path, strrpos( $path, ';' ) ), ';');
+
+               return $path;
+       }
+
+       /**
+        * Show an error message in a box. Parameters are like wfMsg().
+        */
+       function showError( $msg /*...*/ ) {
+               $args = func_get_args();
+               array_shift( $args );
+               $args = array_map( 'htmlspecialchars', $args );
+               $msg = wfMsgReal( $msg, $args, false, false, false );
+               $this->output->addHTML( $this->getErrorBox( $msg ) );
+       }
+
+       /**
+        * Temporary error handler for session start debugging
+        */
+       function errorHandler( $errno, $errstr ) {
+               $this->phpErrors[] = $errstr;
+       }
+
+       /**
+        * Clean up from execute()
+        * @private.
+        */
+       function finish() {
+               $this->output->output();
+               $this->session['happyPages'] = $this->happyPages;
+               $this->session['skippedPages'] = $this->skippedPages;
+               $this->session['settings'] = $this->settings;
+               return $this->session;
+       }
+
+       /**
+        * Get a URL for submission back to the same script
+        */
+       function getUrl( $query = array() ) {
+               $url = $this->request->getRequestURL();
+               # Remove existing query
+               $url = preg_replace( '/\?.*$/', '', $url );
+               if ( $query ) {
+                       $url .= '?' . wfArrayToCGI( $query );
+               }
+               return $url;
+       }
+
+       /**
+        * Get a WebInstallerPage from the main sequence, by ID
+        */
+       function getPageById( $id ) {
+               $pageName = $this->pageSequence[$id];
+               $pageClass = 'WebInstaller_' . $pageName;
+               return new $pageClass( $this );
+       }
+
+       /**
+        * Get a WebInstallerPage by name
+        */
+       function getPageByName( $pageName ) {
+               $pageClass = 'WebInstaller_' . $pageName;
+               return new $pageClass( $this );
+       }
+
+       /**
+        * Get a session variable
+        */
+       function getSession( $name, $default = null ) {
+               if ( !isset( $this->session[$name] ) ) {
+                       return $default;
+               } else {
+                       return $this->session[$name];
+               }
+       }
+
+       /**
+        * Set a session variable
+        */
+       function setSession( $name, $value ) {
+               $this->session[$name] = $value;
+       }
+
+       /**
+        * Get the next tabindex attribute value
+        */
+       function nextTabIndex() {
+               return $this->tabIndex++;
+       }
+
+       /**
+        * Initializes language-related variables
+        */
+       function setupLanguage() {
+               global $wgLang, $wgContLang, $wgLanguageCode;
+               if ( $this->getSession( 'test' ) === null && !$this->request->wasPosted() ) {
+                       $wgLanguageCode = $this->getAcceptLanguage();
+                       $wgLang = $wgContLang = Language::factory( $wgLanguageCode );
+                       $this->setVar( 'wgLanguageCode', $wgLanguageCode );
+                       $this->setVar( '_UserLang', $wgLanguageCode );
+               } else {
+                       $wgLanguageCode = $this->getVar( 'wgLanguageCode' );
+                       $wgLang = Language::factory( $this->getVar( '_UserLang' ) );
+                       $wgContLang = Language::factory( $wgLanguageCode );
+               }
+       }
+
+       /**
+        * Retrieves MediaWiki language from Accept-Language HTTP header
+        */
+       function getAcceptLanguage() {
+               global $wgLanguageCode;
+
+               $mwLanguages = Language::getLanguageNames();
+               $langs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
+               foreach ( explode( ';', $langs ) as $splitted ) {
+                       foreach ( explode( ',', $splitted ) as $lang ) {
+                               $lang = trim( strtolower( $lang ) );
+                               if ( $lang == '' || $lang[0] == 'q' ) {
+                                       continue;
+                               }
+                               if ( isset( $mwLanguages[$lang] ) ) {
+                                       return $lang;
+                               }
+                               $lang = preg_replace( '/^(.*?)(?=-[^-]*)$/', '\\1', $lang );
+                               if ( $lang != '' && isset( $mwLanguages[$lang] ) ) {
+                                       return $lang;
+                               }
+                       }
+               }
+               return $wgLanguageCode;
+       }
+
+       /**
+        * Called by execute() before page output starts, to show a page list
+        */
+       function startPageWrapper( $currentPageName ) {
+               $s = "<div class=\"config-page-wrapper\">\n" .
+                       "<div class=\"config-page-list\"><ul>\n";
+               $lastHappy = -1;
+               foreach ( $this->pageSequence as $id => $pageName ) {
+                       $happy = !empty( $this->happyPages[$id] );
+                       $s .= $this->getPageListItem( $pageName,
+                               $happy || $lastHappy == $id - 1, $currentPageName );
+                       if ( $happy ) {
+                               $lastHappy = $id;
+                       }
+               }
+               $s .= "</ul><br/><ul>\n";
+               foreach ( $this->otherPages as $pageName ) {
+                       $s .= $this->getPageListItem( $pageName, true, $currentPageName );
+               }
+               $s .= "</ul></div>\n". // end list pane
+                       "<div class=\"config-page\">\n" .
+                       Xml::element( 'h2', array(),
+                               wfMsg( 'config-page-' . strtolower( $currentPageName ) ) );
+
+               $this->output->addHTMLNoFlush( $s );
+       }
+
+       /**
+        * Get a list item for the page list
+        */
+       function getPageListItem( $pageName, $enabled, $currentPageName ) {
+               $s = "<li class=\"config-page-list-item\">";
+               $name = wfMsg( 'config-page-' . strtolower( $pageName ) );
+               if ( $enabled ) {
+                       $query = array( 'page' => $pageName );
+                       if ( !in_array( $pageName, $this->pageSequence ) ) {
+                               if ( in_array( $currentPageName, $this->pageSequence ) ) {
+                                       $query['lastPage'] = $currentPageName;
+                               }
+                               $link = Xml::element( 'a',
+                                       array(
+                                               'href' => $this->getUrl( $query )
+                                       ),
+                                       $name
+                               );
+                       } else {
+                               $link = htmlspecialchars( $name );
+                       }
+                       if ( $pageName == $currentPageName ) {
+                               $s .= "<span class=\"config-page-current\">$link</span>";
+                       } else {
+                               $s .= $link;
+                       }
+               } else {
+                       $s .= Xml::element( 'span',
+                               array(
+                                       'class' => 'config-page-disabled'
+                               ),
+                               $name
+                       );
+               }
+               $s .= "</li>\n";
+               return $s;
+       }
+
+       /**
+        * Output some stuff after a page is finished
+        */
+       function endPageWrapper() {
+               $this->output->addHTMLNoFlush(
+                       "</div>\n" .
+                       "<br style=\"clear:both\"/>\n" .
+                       "</div>" );
+       }
+
+       /**
+        * Get HTML for an error box with an icon
+        * @param string $text Wikitext, get this with wfMsgNoTrans()
+        */
+       function getErrorBox( $text ) {
+               return $this->getInfoBox( $text, 'critical-32.png', 'config-error-box' );
+       }
+
+       /**
+        * Get HTML for a warning box with an icon
+        * @param string $text Wikitext, get this with wfMsgNoTrans()
+        */
+       function getWarningBox( $text ) {
+               return $this->getInfoBox( $text, 'warning-32.png', 'config-warning-box' );
+       }
+
+       /**
+        * Get HTML for an info box with an icon
+        * @param string $text Wikitext, get this with wfMsgNoTrans()
+        * @param string $icon Icon name, file in skins/common/images
+        * @param string $class Additional class name to add to the wrapper div
+        */
+       function getInfoBox( $text, $icon = 'info-32.png', $class = false ) {
+               $s =
+                       "<div class=\"config-info $class\">\n" .
+                               "<div class=\"config-info-left\">\n" .
+                               Xml::element( 'img',
+                                       array(
+                                               'src' => '../skins/common/images/' . $icon,
+                                               'alt' => wfMsg( 'config-information' ),
+                                       )
+                               ) . "\n" .
+                               "</div>\n" .
+                               "<div class=\"config-info-right\">\n" .
+                                       $this->parse( $text ) . "\n" .
+                               "</div>\n" .
+                               "<div style=\"clear: left;\"></div>\n" .
+                       "</div>\n";
+               return $s;
+       }
+
+       /**
+        * Get small text indented help for a preceding form field.
+        * Parameters like wfMsg().
+        */
+       function getHelpBox( $msg /*, ... */ ) {
+               $args = func_get_args();
+               array_shift( $args );
+               $args = array_map( 'htmlspecialchars', $args );
+               $text = wfMsgReal( $msg, $args, false, false, false );
+               $html = $this->parse( $text, true );
+               $id = $this->helpId++;
+               $alt = wfMsg( 'help' );
+
+               return
+                       "<div class=\"config-help-wrapper\">\n" .
+                       "<div class=\"config-help-message\">\n" .
+                        $html .
+                       "</div>\n" .
+                       "<div class=\"config-show-help\">\n" .
+                       "<a href=\"#\">" .
+                       wfMsgHtml( 'config-show-help' ) .
+                       "</a></div>\n" .
+                       "<div class=\"config-hide-help\">\n" .
+                       "<a href=\"#\">" .
+                       wfMsgHtml( 'config-hide-help' ) .
+                       "</a></div>\n</div>\n";
+       }
+
+       /**
+        * Output a help box
+        */
+       function showHelpBox( $msg /*, ... */ ) {
+               $args = func_get_args();
+               $html = call_user_func_array( array( $this, 'getHelpBox' ), $args );
+               $this->output->addHTML( $html );
+       }
+
+       /**
+        * Show a short informational message
+        * Output looks like a list.
+        */
+       function showMessage( $msg /*, ... */ ) {
+               $args = func_get_args();
+               array_shift( $args );
+               $html = '<div class="config-message">' .
+                       $this->parse( wfMsgReal( $msg, $args, false, false, false ) ) .
+                       "</div>\n";
+               $this->output->addHTML( $html );
+       }
+
+       /**
+        * Label a control by wrapping a config-input div around it and putting a
+        * label before it
+        */
+       function label( $msg, $forId, $contents ) {
+               if ( strval( $msg ) == '' ) {
+                       $labelText = '&nbsp;';
+               } else {
+                       $labelText = wfMsgHtml( $msg );
+               }
+               $attributes = array( 'class' => 'config-label' );
+               if ( $forId ) {
+                       $attributes['for'] = $forId;
+               }
+               return
+                       "<div class=\"config-input\">\n" .
+                       Xml::tags( 'label',
+                               $attributes,
+                               $labelText ) . "\n" .
+                       $contents .
+                       "</div>\n";
+       }
+
+       /**
+        * Get a labelled text box to configure a variable
+        * @param array $params
+        *    Parameters are:
+        *      var:        The variable to be configured (required)
+        *      label:      The message name for the label (required)
+        *      attribs:    Additional attributes for the input element (optional)
+        *      controlName: The name for the input element (optional)
+        *      value:      The current value of the variable (optional)
+        */
+       function getTextBox( $params ) {
+               if ( !isset( $params['controlName'] ) ) {
+                       $params['controlName'] = 'config_' . $params['var'];
+               }
+               if ( !isset( $params['value'] ) ) {
+                       $params['value'] = $this->getVar( $params['var'] );
+               }
+               if ( !isset( $params['attribs'] ) ) {
+                       $params['attribs'] = array();
+               }
+               return
+                       $this->label(
+                               $params['label'],
+                               $params['controlName'],
+                               Xml::input(
+                                       $params['controlName'],
+                                       30, // intended to be overridden by CSS
+                                       $params['value'],
+                                       $params['attribs'] + array(
+                                               'id' => $params['controlName'],
+                                               'class' => 'config-input-text',
+                                               'tabindex' => $this->nextTabIndex()
+                                       )
+                               )
+                       );
+       }
+
+       /**
+        * Get a labelled password box to configure a variable
+        * Implements password hiding
+        * @param array $params
+        *    Parameters are:
+        *      var:        The variable to be configured (required)
+        *      label:      The message name for the label (required)
+        *      attribs:    Additional attributes for the input element (optional)
+        *      controlName: The name for the input element (optional)
+        *      value:      The current value of the variable (optional)
+        */
+       function getPasswordBox( $params ) {
+               if ( !isset( $params['value'] ) ) {
+                       $params['value'] = $this->getVar( $params['var'] );
+               }
+               if ( !isset( $params['attribs'] ) ) {
+                       $params['attribs'] = array();
+               }
+               $params['value'] = $this->getFakePassword( $params['value'] );
+               $params['attribs']['type'] = 'password';
+               return $this->getTextBox( $params );
+       }
+
+       /**
+        * Get a labelled checkbox to configure a boolean variable
+        * @param array $params
+        *    Parameters are:
+        *      var:        The variable to be configured (required)
+        *      label:      The message name for the label (required)
+        *      attribs:    Additional attributes for the input element (optional)
+        *      controlName: The name for the input element (optional)
+        *      value:      The current value of the variable (optional)
+        */
+       function getCheckBox( $params ) {
+               if ( !isset( $params['controlName'] ) ) {
+                       $params['controlName'] = 'config_' . $params['var'];
+               }
+               if ( !isset( $params['value'] ) ) {
+                       $params['value'] = $this->getVar( $params['var'] );
+               }
+               if ( !isset( $params['attribs'] ) ) {
+                       $params['attribs'] = array();
+               }
+               if( isset( $params['rawtext'] ) ) {
+                       $labelText = $params['rawtext'];
+               } else {
+                       $labelText = $this->parse( wfMsg( $params['label'] ) );
+               }
+               return
+                       "<div class=\"config-input-check\">\n" .
+                       "<label>\n" .
+                       Xml::check(
+                               $params['controlName'],
+                               $params['value'],
+                               $params['attribs'] + array(
+                                       'id' => $params['controlName'],
+                                       'class' => 'config-input-text',
+                                       'tabindex' => $this->nextTabIndex(),
+                               )
+                       ) .
+                       $labelText . "\n" .
+                       "</label>\n" .
+                       "</div>\n";
+       }
+
+       /**
+        * Get a set of labelled radio buttons
+        *
+        * @param array $params
+        *    Parameters are:
+        *      var:            The variable to be configured (required)
+        *      label:          The message name for the label (required)
+        *      itemLabelPrefix: The message name prefix for the item labels (required)
+        *      values:         List of allowed values (required)
+        *      itemAttribs     Array of attribute arrays, outer key is the value name (optional)
+        *      commonAttribs   Attribute array applied to all items
+        *      controlName:    The name for the input element (optional)
+        *      value:          The current value of the variable (optional)
+        */
+       function getRadioSet( $params ) {
+               if ( !isset( $params['controlName']  ) ) {
+                       $params['controlName'] = 'config_' . $params['var'];
+               }
+               if ( !isset( $params['value'] ) ) {
+                       $params['value'] = $this->getVar( $params['var'] );
+               }
+               if ( !isset( $params['label'] ) ) {
+                       $label = '';
+               } else {
+                       $label = $this->parse( wfMsgNoTrans( $params['label'] ) );
+               }
+               $s = "<label class=\"config-label\">\n" .
+                       $label .
+                       "</label>\n" .
+                       "<ul class=\"config-settings-block\">\n";
+               foreach ( $params['values'] as $value ) {
+                       $itemAttribs = array();
+                       if ( isset( $params['commonAttribs'] ) ) {
+                               $itemAttribs = $params['commonAttribs'];
+                       }
+                       if ( isset( $params['itemAttribs'][$value] ) ) {
+                               $itemAttribs = $params['itemAttribs'][$value] + $itemAttribs;
+                       }
+                       $checked = $value == $params['value'];
+                       $id = $params['controlName'] . '_' . $value;
+                       $itemAttribs['id'] = $id;
+                       $itemAttribs['tabindex'] = $this->nextTabIndex();
+                       $s .=
+                               '<li>' .
+                               Xml::radio( $params['controlName'], $value, $checked, $itemAttribs ) .
+                               '&nbsp;' .
+                               Xml::tags( 'label', array( 'for' => $id ), $this->parse(
+                                       wfMsgNoTrans( $params['itemLabelPrefix'] . strtolower( $value ) )
+                               ) ) .
+                               "</li>\n";
+               }
+               $s .= "</ul>\n";
+               return $s;
+       }
+
+       /**
+        * Output an error box using a Status object
+        */
+       function showStatusErrorBox( $status ) {
+               $text = $status->getWikiText();
+               $this->output->addHTML( $this->getErrorBox( $text ) );
+       }
+
+       function showStatusError( $status ) {
+               $text = $status->getWikiText();
+               $this->output->addWikiText(
+                       "<div class=\"config-message\">\n" .
+                       $text .
+                       "</div>"
+               );
+       }
+
+       /**
+        * Convenience function to set variables based on form data.
+        * Assumes that variables containing "password" in the name are (potentially
+        * fake) passwords.
+        * @param array $varNames
+        * @param string $prefix The prefix added to variables to obtain form names
+        */
+       function setVarsFromRequest( $varNames, $prefix = 'config_' ) {
+               $newValues = array();
+               foreach ( $varNames as $name ) {
+                       $value = trim( $this->request->getVal( $prefix . $name ) );
+                       $newValues[$name] = $value;
+                       if ( $value === null ) {
+                               // Checkbox?
+                               $this->setVar( $name, false );
+                       } else {
+                               if ( stripos( $name, 'password' ) !== false ) {
+                                       $this->setPassword( $name, $value );
+                               } else {
+                                       $this->setVar( $name, $value );
+                               }
+                       }
+               }
+               return $newValues;
+       }
+
+       /**
+        * Get the starting tags of a fieldset
+        * @param string $legend Message name
+        */
+       function getFieldsetStart( $legend ) {
+               return "\n<fieldset><legend>" . wfMsgHtml( $legend ) . "</legend>\n";
+       }
+
+       /**
+        * Get the end tag of a fieldset
+        */
+       function getFieldsetEnd() {
+               return "</fieldset>\n";
+       }
+
+       /**
+        * Helper for Installer::docLink()
+        */
+       function getDocUrl( $page ) {
+               $url = "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page );
+               if ( in_array( $this->currentPageName, $this->pageSequence ) ) {
+                       $url .= '&lastPage=' . urlencode( $this->currentPageName );
+               }
+               return $url;
+       }
+}
+
+class WebInstallerPage {
+       function __construct( $parent ) {
+               $this->parent = $parent;
+       }
+
+       function addHTML( $html ) {
+               $this->parent->output->addHTML( $html );
+       }
+
+       function startForm() {
+               $this->addHTML(
+                       "<div class=\"config-section\">\n" .
+                       Xml::openElement(
+                               'form',
+                               array(
+                                       'method' => 'post',
+                                       'action' => $this->parent->getUrl( array( 'page' => $this->getName() ) )
+                               )
+                       ) . "\n"
+               );
+       }
+
+       function endForm( $continue = 'continue' ) {
+               $this->parent->output->outputWarnings();
+               $s = "<div class=\"config-submit\">\n";
+               $id = $this->getId();
+               if ( $id === false ) {
+                       $s .= Xml::hidden( 'lastPage', $this->parent->request->getVal( 'lastPage' ) );
+               }
+               if ( $continue ) {
+                       // Fake submit button for enter keypress
+                       $s .= Xml::submitButton( wfMsg( "config-$continue" ),
+                               array( 'name' => "enter-$continue", 'style' => 'display:none' ) ) . "\n";
+               }
+               if ( $id !== 0 ) {
+                       $s .= Xml::submitButton( wfMsg( 'config-back' ),
+                               array(
+                                       'name' => 'submit-back',
+                                       'tabindex' => $this->parent->nextTabIndex()
+                               ) ) . "\n";
+               }
+               if ( $continue ) {
+                       $s .= Xml::submitButton( wfMsg( "config-$continue" ),
+                               array(
+                                       'name' => "submit-$continue",
+                                       'tabindex' => $this->parent->nextTabIndex(),
+                               ) ) . "\n";
+               }
+               $s .= "</div></form></div>\n";
+               $this->addHTML( $s );
+       }
+
+       function getName() {
+               return str_replace( 'WebInstaller_', '', get_class( $this ) );
+       }
+
+       function getId() {
+               return array_search( $this->getName(), $this->parent->pageSequence );
+       }
+
+       function execute() {
+               if ( $this->parent->request->wasPosted() ) {
+                       return 'continue';
+               } else {
+                       $this->startForm();
+                       $this->addHTML( 'Mockup' );
+                       $this->endForm();
+               }
+       }
+
+       function getVar( $var ) {
+               return $this->parent->getVar( $var );
+       }
+
+       function setVar( $name, $value ) {
+               $this->parent->setVar( $name, $value );
+       }
+}
+
+class WebInstaller_Language extends WebInstallerPage {
+       function execute() {
+               global $wgLang;
+               $r = $this->parent->request;
+               $userLang = $r->getVal( 'UserLang' );
+               $contLang = $r->getVal( 'ContLang' );
+
+               $lifetime = intval( ini_get( 'session.gc_maxlifetime' ) );
+               if ( !$lifetime ) {
+                       $lifetime = 1440; // PHP default
+               }
+
+               if ( $r->wasPosted() ) {
+                       # Do session test
+                       if ( $this->parent->getSession( 'test' ) === null ) {
+                               $requestTime = $r->getVal( 'LanguageRequestTime' );
+                               if ( !$requestTime ) {
+                                       // The most likely explanation is that the user was knocked back
+                                       // from another page on POST due to session expiry
+                                       $msg = 'config-session-expired';
+                               } elseif ( time() - $requestTime > $lifetime ) {
+                                       $msg = 'config-session-expired';
+                               } else {
+                                       $msg = 'config-no-session';
+                               }
+                               $this->parent->showError( $msg, $wgLang->formatTimePeriod( $lifetime ) );
+                       } else {
+                               $languages = Language::getLanguageNames();
+                               if ( isset( $languages[$userLang] ) ) {
+                                       $this->setVar( '_UserLang', $userLang );
+                               }
+                               if ( isset( $languages[$contLang] ) ) {
+                                       $this->setVar( 'wgLanguageCode', $contLang );
+                                       if ( $this->getVar( '_AdminName' ) === null ) {
+                                               // Load localised sysop username in *content* language
+                                               $this->setVar( '_AdminName', wfMsgForContent( 'config-admin-default-username' ) );
+                                       }
+                               }
+                               return 'continue';
+                       }
+               } elseif ( $this->parent->showSessionWarning ) {
+                       # The user was knocked back from another page to the start
+                       # This probably indicates a session expiry
+                       $this->parent->showError( 'config-session-expired', $wgLang->formatTimePeriod( $lifetime ) );
+               }
+
+               $this->parent->setSession( 'test', true );
+
+               if ( !isset( $languages[$userLang] ) ) {
+                       $userLang = $this->getVar( '_UserLang', 'en' );
+               }
+               if ( !isset( $languages[$contLang] ) ) {
+                       $contLang = $this->getVar( 'wgLanguageCode', 'en' );
+               }
+               $this->startForm();
+               $s =
+                       Xml::hidden( 'LanguageRequestTime', time() ) .
+                       $this->getLanguageSelector( 'UserLang', 'config-your-language', $userLang ) .
+                       $this->parent->getHelpBox( 'config-your-language-help' ) .
+                       $this->getLanguageSelector( 'ContLang', 'config-wiki-language', $contLang ) .
+                       $this->parent->getHelpBox( 'config-wiki-language-help' );
+
+
+               $this->addHTML( $s );
+               $this->endForm();
+       }
+
+       /**
+        * Get a <select> for selecting languages
+        */
+       function getLanguageSelector( $name, $label, $selectedCode ) {
+               global $wgDummyLanguageCodes;
+               $s = Xml::openElement( 'select', array( 'id' => $name, 'name' => $name ) ) . "\n";
+
+               $languages = Language::getLanguageNames();
+               ksort( $languages );
+               $dummies = array_flip( $wgDummyLanguageCodes );
+               foreach ( $languages as $code => $lang ) {
+                       if ( isset( $dummies[$code] ) ) continue;
+                       $s .= "\n" . Xml::option( "$code - $lang", $code, $code == $selectedCode );
+               }
+               $s .= "\n</select>\n";
+               return $this->parent->label( $label, $name, $s );
+       }
+}
+
+class WebInstaller_Welcome extends WebInstallerPage {
+       function execute() {
+               if ( $this->parent->request->wasPosted() ) {
+                       if ( $this->getVar( '_Environment' ) ) {
+                               return 'continue';
+                       }
+               }
+               $this->parent->output->addWikiText( wfMsgNoTrans( 'config-welcome' ) );
+               $status = $this->parent->doEnvironmentChecks();
+               if ( $status ) {
+                       $this->parent->output->addWikiText( wfMsgNoTrans( 'config-copyright', wfMsg( 'config-authors' ) ) );
+                       $this->startForm();
+                       $this->endForm();
+               }
+       }
+}
+
+class WebInstaller_DBConnect extends WebInstallerPage {
+       function execute() {
+               $r = $this->parent->request;
+               if ( $r->wasPosted() ) {
+                       $status = $this->submit();
+                       if ( $status->isGood() ) {
+                               $this->setVar( '_UpgradeDone', false );
+                               return 'continue';
+                       } else {
+                               $this->parent->showStatusErrorBox( $status );
+                       }
+               }
+
+
+               $this->startForm();
+
+               $types = "<ul class=\"config-settings-block\">\n";
+               $settings = '';
+               $defaultType = $this->getVar( 'wgDBtype' );
+               foreach ( $this->parent->getVar( '_CompiledDBs' ) as $type ) {
+                       $installer = $this->parent->getDBInstaller( $type );
+                       $types .=
+                               '<li>' .
+                               Xml::radioLabel(
+                                       $installer->getReadableName(),
+                                       'DBType',
+                                       $type,
+                                       "DBType_$type",
+                                       $type == $defaultType,
+                                       array( 'class' => 'dbRadio', 'rel' => "DB_wrapper_$type" )
+                               ) .
+                               "</li>\n";
+
+                       $settings .=
+                               Xml::openElement( 'div', array( 'id' => 'DB_wrapper_' . $type, 'class' => 'dbWrapper' ) ) .
+                               Xml::element( 'h3', array(), wfMsg( 'config-header-' . $type ) ) .
+                               $installer->getConnectForm() .
+                               "</div>\n";
+               }
+               $types .= "</ul><br clear=\"left\"/>\n";
+
+               $this->addHTML(
+                       $this->parent->label( 'config-db-type', false, $types ) .
+                       $settings
+               );
+
+               $this->endForm();
+       }
+
+       function submit() {
+               $r = $this->parent->request;
+               $type = $r->getVal( 'DBType' );
+               $this->setVar( 'wgDBtype', $type );
+               $installer = $this->parent->getDBInstaller( $type );
+               if ( !$installer ) {
+                       return Status::newFatal( 'config-invalid-db-type' );
+               }
+               return $installer->submitConnectForm();
+       }
+}
+
+class WebInstaller_Upgrade extends WebInstallerPage {
+       function execute() {
+               if ( $this->getVar( '_UpgradeDone' ) ) {
+                       if ( $this->parent->request->wasPosted() ) {
+                               // Done message acknowledged
+                               return 'continue';
+                       } else {
+                               // Back button click
+                               // Show the done message again
+                               // Make them click back again if they want to do the upgrade again
+                               $this->showDoneMessage();
+                               return 'output';
+                       }
+               }
+
+               // wgDBtype is generally valid here because otherwise the previous page
+               // (connect) wouldn't have declared its happiness
+               $type = $this->getVar( 'wgDBtype' );
+               $installer = $this->parent->getDBInstaller( $type );
+
+               if ( !$installer->needsUpgrade() ) {
+                       return 'skip';
+               }
+
+               if ( $this->parent->request->wasPosted() ) {
+                       $this->addHTML(
+                               '<div id="config-spinner" style="display:none;"><img src="../skins/common/images/ajax-loader.gif" /></div>' .
+                               '<script>jQuery( "#config-spinner" )[0].style.display = "block";</script>' .
+                               '<textarea id="config-update-log" name="UpdateLog" rows="10" readonly="readonly">'
+                       );
+                       $this->parent->output->flush();
+                       $result = $installer->doUpgrade();
+                       $this->addHTML( '</textarea>
+<script>jQuery( "#config-spinner" )[0].style.display = "none";</script>' );
+                       $this->parent->output->flush();
+                       if ( $result ) {
+                               $this->setVar( '_UpgradeDone', true );
+                               $this->showDoneMessage();
+                               return 'output';
+                       }
+               }
+
+               $this->startForm();
+               $this->addHTML( $this->parent->getInfoBox(
+                       wfMsgNoTrans( 'config-can-upgrade', $GLOBALS['wgVersion'] ) ) );
+               $this->endForm();
+       }
+
+       function showDoneMessage() {
+               $this->startForm();
+               $this->addHTML(
+                       $this->parent->getInfoBox(
+                               wfMsgNoTrans( 'config-upgrade-done',
+                                       $GLOBALS['wgServer'] .
+                                               $this->getVar( 'wgScriptPath' ) . '/index' .
+                                               $this->getVar( 'wgScriptExtension' )
+                               ), 'tick-32.png'
+                       )
+               );
+               $this->endForm( 'regenerate' );
+       }
+}
+
+class WebInstaller_DBSettings extends WebInstallerPage {
+       function execute() {
+               $installer = $this->parent->getDBInstaller( $this->getVar( 'wgDBtype' ) );
+
+               $r = $this->parent->request;
+               if ( $r->wasPosted() ) {
+                       $status = $installer->submitSettingsForm();
+                       if ( $status === false ) {
+                               return 'skip';
+                       } elseif ( $status->isGood() ) {
+                               return 'continue';
+                       } else {
+                               $this->parent->showStatusErrorBox( $status );
+                       }
+               }
+
+               $form = $installer->getSettingsForm();
+               if ( $form === false ) {
+                       return 'skip';
+               }
+
+               $this->startForm();
+               $this->addHTML( $form );
+               $this->endForm();
+       }
+
+}
+
+class WebInstaller_Name extends WebInstallerPage {
+       function execute() {
+               $r = $this->parent->request;
+               if ( $r->wasPosted() ) {
+                       if ( $this->submit() ) {
+                               return 'continue';
+                       }
+               }
+
+               $this->startForm();
+
+               if ( $this->getVar( 'wgSitename' ) == $GLOBALS['wgSitename'] ) {
+                       $this->setVar( 'wgSitename', '' );
+               }
+
+               // Set wgMetaNamespace to something valid before we show the form.
+               // $wgMetaNamespace defaults to $wgSiteName which is 'MediaWiki'
+               $metaNS = $this->getVar( 'wgMetaNamespace' );
+               $this->setVar( 'wgMetaNamespace', wfMsgForContent( 'config-ns-other-default' ) );
+
+               $this->addHTML(
+                       $this->parent->getTextBox( array(
+                               'var' => 'wgSitename',
+                               'label' => 'config-site-name',
+                       ) ) .
+                       $this->parent->getHelpBox( 'config-site-name-help' ) .
+                       $this->parent->getRadioSet( array(
+                               'var' => '_NamespaceType',
+                               'label' => 'config-project-namespace',
+                               'itemLabelPrefix' => 'config-ns-',
+                               'values' => array( 'site-name', 'generic', 'other' ),
+                               'commonAttribs' => array( 'class' => 'enableForOther', 'rel' => 'config_wgMetaNamespace' ),
+                       ) ) .
+                       $this->parent->getTextBox( array(
+                               'var' => 'wgMetaNamespace',
+                               'label' => '',
+                               'attribs' => array( 'disabled' => '' ),
+                       ) ) .
+                       $this->parent->getHelpBox( 'config-project-namespace-help' ) .
+                       $this->parent->getFieldsetStart( 'config-admin-box' ) .
+                       $this->parent->getTextBox( array(
+                               'var' => '_AdminName',
+                               'label' => 'config-admin-name'
+                       ) ) .
+                       $this->parent->getPasswordBox( array(
+                               'var' => '_AdminPassword',
+                               'label' => 'config-admin-password',
+                       ) ) .
+                       $this->parent->getPasswordBox( array(
+                               'var' => '_AdminPassword2',
+                               'label' => 'config-admin-password-confirm'
+                       ) ) .
+                       $this->parent->getHelpBox( 'config-admin-help' ) .
+                       $this->parent->getTextBox( array(
+                               'var' => '_AdminEmail',
+                               'label' => 'config-admin-email'
+                       ) ) .
+                       $this->parent->getHelpBox( 'config-admin-email-help' ) .
+                       $this->parent->getCheckBox( array(
+                               'var' => '_Subscribe',
+                               'label' => 'config-subscribe'
+                       ) ) .
+                       $this->parent->getHelpBox( 'config-subscribe-help' ) .
+                       $this->parent->getFieldsetEnd() .
+                       $this->parent->getInfoBox( wfMsg( 'config-almost-done' ) ) .
+                       $this->parent->getRadioSet( array(
+                               'var' => '_SkipOptional',
+                               'itemLabelPrefix' => 'config-optional-',
+                               'values' => array( 'continue', 'skip' )
+                       ) )
+               );
+
+               // Restore the default value
+               $this->setVar( 'wgMetaNamespace', $metaNS );
+
+               $this->endForm();
+               return 'output';
+       }
+
+       function submit() {
+               $retVal = true;
+               $this->parent->setVarsFromRequest( array( 'wgSitename', '_NamespaceType',
+                       '_AdminName', '_AdminPassword', '_AdminPassword2', '_AdminEmail',
+                       '_Subscribe', '_SkipOptional' ) );
+
+               // Validate site name
+               if ( strval( $this->getVar( 'wgSitename' ) ) === '' ) {
+                       $this->parent->showError( 'config-site-name-blank' );
+                       $retVal = false;
+               }
+
+               // Fetch namespace
+               $nsType = $this->getVar( '_NamespaceType' );
+               if ( $nsType == 'site-name' ) {
+                       $name = $this->getVar( 'wgSitename' );
+                       // Sanitize for namespace
+                       // This algorithm should match the JS one in WebInstallerOutput.php
+                       $name = preg_replace( '/[\[\]\{\}|#<>%+? ]/', '_', $name );
+                       $name = str_replace( '&', '&amp;', $name );
+                       $name = preg_replace( '/__+/', '_', $name );
+                       $name = ucfirst( trim( $name, '_' ) );
+               } elseif ( $nsType == 'generic' ) {
+                       $name = wfMsg( 'config-ns-generic' );
+               } else { // other
+                       $name = $this->getVar( 'wgMetaNamespace' );
+               }
+
+               // Validate namespace
+               if ( strpos( $name, ':' ) !== false ) {
+                       $good = false;
+               } else {
+                       // Title-style validation
+                       $title = Title::newFromText( $name );
+                       if ( !$title ) {
+                               $good = $nsType == 'site-name' ? true : false;
+                       } else {
+                               $name = $title->getDBkey();
+                               $good = true;
+                       }
+               }
+               if ( !$good ) {
+                       $this->parent->showError( 'config-ns-invalid', $name );
+                       $retVal = false;
+               }
+               $this->setVar( 'wgMetaNamespace', $name );
+
+               // Validate username for creation
+               $name = $this->getVar( '_AdminName' );
+               if ( strval( $name ) === '' ) {
+                       $this->parent->showError( 'config-admin-name-blank' );
+                       $cname = $name;
+                       $retVal = false;
+               } else {
+                       $cname = User::getCanonicalName( $name, 'creatable' );
+                       if ( $cname === false ) {
+                               $this->parent->showError( 'config-admin-name-invalid', $name );
+                               $retVal = false;
+                       } else {
+                               $this->setVar( '_AdminName', $cname );
+                       }
+               }
+
+               // Validate password
+               $msg = false;
+               $pwd = $this->getVar( '_AdminPassword' );
+               $user = User::newFromName( $cname );
+               $valid = $user->getPasswordValidity( $pwd );
+               if ( strval( $pwd ) === '' ) {
+                       # $user->getPasswordValidity just checks for $wgMinimalPasswordLength.
+                       # This message is more specific and helpful.
+                       $msg = 'config-admin-password-blank';
+               } elseif ( $pwd !== $this->getVar( '_AdminPassword2' ) ) {
+                       $msg = 'config-admin-password-mismatch';
+               } elseif ( $valid !== true ) {
+                       # As of writing this will only catch the username being e.g. 'FOO' and
+                       # the password 'foo'
+                       $msg = $valid;
+               }
+               if ( $msg !== false ) {
+                       $this->parent->showError( $msg );
+                       $this->setVar( '_AdminPassword', '' );
+                       $this->setVar( '_AdminPassword2', '' );
+                       $retVal = false;
+               }
+               return $retVal;
+       }
+}
+
+class WebInstaller_Options extends WebInstallerPage {
+       function execute() {
+               if ( $this->getVar( '_SkipOptional' ) == 'skip' ) {
+                       return 'skip';
+               }
+               if ( $this->parent->request->wasPosted() ) {
+                       if ( $this->submit() ) {
+                               return 'continue';
+                       }
+               }
+
+               $this->startForm();
+               $this->addHTML(
+                       # User Rights
+                       $this->parent->getRadioSet( array(
+                               'var' => '_RightsProfile',
+                               'label' => 'config-profile',
+                               'itemLabelPrefix' => 'config-profile-',
+                               'values' => array_keys( $this->parent->rightsProfiles ),
+                       ) ) .
+                       $this->parent->getHelpBox( 'config-profile-help' ) .
+
+                       # Licensing
+                       $this->parent->getRadioSet( array(
+                               'var' => '_LicenseCode',
+                               'label' => 'config-license',
+                               'itemLabelPrefix' => 'config-license-',
+                               'values' => array_keys( $this->parent->licenses ),
+                               'commonAttribs' => array( 'class' => 'licenseRadio' ),
+                       ) ) .
+                       $this->getCCChooser() .
+                       $this->parent->getHelpBox( 'config-license-help' ) .
+
+                       # E-mail
+                       $this->parent->getFieldsetStart( 'config-email-settings' ) .
+                       $this->parent->getCheckBox( array(
+                               'var' => 'wgEnableEmail',
+                               'label' => 'config-enable-email',
+                               'attribs' => array( 'class' => 'showHideRadio', 'rel' => 'emailwrapper' ),
+                       ) ) .
+                       $this->parent->getHelpBox( 'config-enable-email-help' ) .
+                       "<div id=\"emailwrapper\">" .
+                       $this->parent->getTextBox( array(
+                               'var' => 'wgPasswordSender',
+                               'label' => 'config-email-sender'
+                       ) ) .
+                       $this->parent->getHelpBox( 'config-email-sender-help' ) .
+                       $this->parent->getCheckBox( array(
+                               'var' => 'wgEnableUserEmail',
+                               'label' => 'config-email-user',
+                       ) ) .
+                       $this->parent->getHelpBox( 'config-email-user-help' ) .
+                       $this->parent->getCheckBox( array(
+                               'var' => 'wgEnotifUserTalk',
+                               'label' => 'config-email-usertalk',
+                       ) ) .
+                       $this->parent->getHelpBox( 'config-email-usertalk-help' ) .
+                       $this->parent->getCheckBox( array(
+                               'var' => 'wgEnotifWatchlist',
+                               'label' => 'config-email-watchlist',
+                       ) ) .
+                       $this->parent->getHelpBox( 'config-email-watchlist-help' ) .
+                       $this->parent->getCheckBox( array(
+                               'var' => 'wgEmailAuthentication',
+                               'label' => 'config-email-auth',
+                       ) ) .
+                       $this->parent->getHelpBox( 'config-email-auth-help' ) .
+                       "</div>" .
+                       $this->parent->getFieldsetEnd()
+               );
+
+               $extensions = $this->parent->findExtensions();
+               if( $extensions ) {
+                       $extHtml = $this->parent->getFieldsetStart( 'config-extensions' );
+                       foreach( $extensions as $ext ) {
+                               $extHtml .= $this->parent->getCheckBox( array(
+                                       'var' => "ext-$ext",
+                                       'rawtext' => $ext,
+                               ) );
+                       }
+                       $extHtml .= $this->parent->getHelpBox( 'config-extensions-help' ) .
+                               $this->parent->getFieldsetEnd();
+                       $this->addHTML( $extHtml );
+               }
+
+               $this->addHTML(
+                       # Uploading
+                       $this->parent->getFieldsetStart( 'config-upload-settings' ) .
+                       $this->parent->getCheckBox( array( 
+                               'var' => 'wgEnableUploads',
+                               'label' => 'config-upload-enable',
+                               'attribs' => array( 'class' => 'showHideRadio', 'rel' => 'uploadwrapper' ),
+                       ) ) .
+                       $this->parent->getHelpBox( 'config-upload-help' ) .
+                       '<div id="uploadwrapper" style="display: none;">' .
+                       $this->parent->getTextBox( array( 
+                               'var' => 'wgDeletedDirectory',
+                               'label' => 'config-upload-deleted',
+                       ) ) .
+                       $this->parent->getHelpBox( 'config-upload-deleted-help' ) .
+                       $this->parent->getTextBox( array(
+                               'var' => 'wgLogo',
+                               'label' => 'config-logo'
+                       ) ) .
+                       $this->parent->getHelpBox( 'config-logo-help' ) .
+                       '</div>' .
+                       $this->parent->getFieldsetEnd()
+               );
+
+               $caches = array( 'none', 'anything', 'db' );
+               $selected = 'db';
+               if( count( $this->getVar( '_Caches' ) ) ) {
+                       $caches[] = 'accel';
+                       $selected = 'accel';
+               }
+               $caches[] = 'memcached';
+
+               $this->addHTML(
+                       # Advanced settings
+                       $this->parent->getFieldsetStart( 'config-advanced-settings' ) .
+                       # Object cache settings
+                       $this->parent->getRadioSet( array(
+                               'var' => 'wgMainCacheType',
+                               'label' => 'config-cache-options',
+                               'itemLabelPrefix' => 'config-cache-',
+                               'values' => $caches,
+                               'value' => $selected,
+                       ) ) .
+                       $this->parent->getHelpBox( 'config-cache-help' ) .
+                       $this->parent->getTextBox( array(
+                               'var' => '_MemCachedServers',
+                               'label' => 'config-memcached-servers',
+                       ) ) .
+                       $this->parent->getHelpBox( 'config-memcached-help' ) .
+                       $this->parent->getFieldsetEnd()
+               );
+               $this->endForm();
+       }
+
+       function getCCPartnerUrl() {
+               global $wgServer;
+               $exitUrl = $wgServer . $this->parent->getUrl( array(
+                       'page' => 'Options',
+                       'SubmitCC' => 'indeed',
+                       'config__LicenseCode' => 'cc',
+                       'config_wgRightsUrl' => '[license_url]',
+                       'config_wgRightsText' => '[license_name]',
+                       'config_wgRightsIcon' => '[license_button]',
+               ) );
+               $styleUrl = $wgServer . dirname( dirname( $this->parent->getUrl() ) ) .
+                       '/skins/common/config-cc.css';
+               $iframeUrl = 'http://creativecommons.org/license/?' .
+                       wfArrayToCGI( array(
+                               'partner' => 'MediaWiki',
+                               'exit_url' => $exitUrl,
+                               'lang' => $this->getVar( '_UserLang' ),
+                               'stylesheet' => $styleUrl,
+                       ) );
+               return $iframeUrl;
+       }
+
+       function getCCChooser() {
+               $iframeAttribs = array(
+                       'class' => 'config-cc-iframe',
+                       'name' => 'config-cc-iframe',
+                       'id' => 'config-cc-iframe',
+                       'frameborder' => 0,
+                       'width' => '100%',
+                       'height' => '100%',
+               );
+               if ( $this->getVar( '_CCDone' ) ) {
+                       $iframeAttribs['src'] = $this->parent->getUrl( array( 'ShowCC' => 'yes' ) );
+               } else {
+                       $iframeAttribs['src'] = $this->getCCPartnerUrl();
+               }
+
+               return
+                       "<div class=\"config-cc-wrapper\" id=\"config-cc-wrapper\" style=\"display: none;\">\n" .
+                       Xml::element( 'iframe', $iframeAttribs, '', false /* not short */ ) .
+                       "</div>\n";
+       }
+
+       function getCCDoneBox() {
+               $js = "parent.document.getElementById('config-cc-wrapper').style.height = '$1';";
+               // If you change this height, also change it in config.css
+               $expandJs = str_replace( '$1', '54em', $js );
+               $reduceJs = str_replace( '$1', '70px', $js );
+               return
+                       '<p>'.
+                       Xml::element( 'img', array( 'src' => $this->getVar( 'wgRightsIcon' ) ) ) .
+                       '&nbsp;&nbsp;' .
+                       htmlspecialchars( $this->getVar( 'wgRightsText' ) ) .
+                       "</p>\n" .
+                       "<p style=\"text-align: center\">" .
+                       Xml::element( 'a',
+                               array(
+                                       'href' => $this->getCCPartnerUrl(),
+                                       'onclick' => $expandJs,
+                               ),
+                               wfMsg( 'config-cc-again' )
+                       ) .
+                       "</p>\n" .
+                       "<script type=\"text/javascript\">\n" .
+                       # Reduce the wrapper div height
+                       htmlspecialchars( $reduceJs ) .
+                       "\n" .
+                       "</script>\n";
+       }
+
+
+       function submitCC() {
+               $newValues = $this->parent->setVarsFromRequest(
+                       array( 'wgRightsUrl', 'wgRightsText', 'wgRightsIcon' ) );
+               if ( count( $newValues ) != 3 ) {
+                       $this->parent->showError( 'config-cc-error' );
+                       return;
+               }
+               $this->setVar( '_CCDone', true );
+               $this->addHTML( $this->getCCDoneBox() );
+       }
+
+       function submit() {
+               $this->parent->setVarsFromRequest( array( '_RightsProfile', '_LicenseCode',
+                       'wgEnableEmail', 'wgPasswordSender', 'wgEnableUpload', 'wgLogo',
+                       'wgEnableUserEmail', 'wgEnotifUserTalk', 'wgEnotifWatchlist',
+                       'wgEmailAuthentication', 'wgMainCacheType', '_MemCachedServers' ) );
+
+               if ( !in_array( $this->getVar( '_RightsProfile' ),
+                       array_keys( $this->parent->rightsProfiles ) ) )
+               {
+                       reset( $this->parent->rightsProfiles );
+                       $this->setVar( '_RightsProfile', key( $this->parent->rightsProfiles ) );
+               }
+
+               $code = $this->getVar( '_LicenseCode' );
+               if ( $code == 'cc-choose' ) {
+                       if ( !$this->getVar( '_CCDone' ) ) {
+                               $this->parent->showError( 'config-cc-not-chosen' );
+                               return false;
+                       }
+               } elseif ( in_array( $code, array_keys( $this->parent->licenses ) ) ) {
+                       $entry = $this->parent->licenses[$code];
+                       if ( isset( $entry['text'] ) ) {
+                               $this->setVar( 'wgRightsText', $entry['text'] );
+                       } else {
+                               $this->setVar( 'wgRightsText', wfMsg( 'config-license-' . $code ) );
+                       }
+                       $this->setVar( 'wgRightsUrl', $entry['url'] );
+                       $this->setVar( 'wgRightsIcon', $entry['icon'] );
+               } else {
+                       $this->setVar( 'wgRightsText', '' );
+                       $this->setVar( 'wgRightsUrl', '' );
+                       $this->setVar( 'wgRightsIcon', '' );
+               }
+
+               $exts = $this->parent->getVar( '_Extensions' );
+               foreach( $exts as $key => $ext ) {
+                       if( !$this->parent->request->getCheck( 'config_ext-' . $ext ) ) {
+                               unset( $exts[$key] );
+                       }
+               }
+               $this->parent->setVar( '_Extensions', $exts );
+               return true;
+       }
+}
+
+class WebInstaller_Install extends WebInstallerPage {
+
+       function execute() {
+               if( $this->parent->request->wasPosted() ) {
+                       return 'continue';
+               }
+               $this->startForm();
+               $this->addHTML("<ul>");
+               foreach( $this->parent->getInstallSteps() as $step ) {
+                       $this->startStage( "config-install-$step" );
+                       $func = 'install' . ucfirst( $step );
+                       $status = $this->parent->{$func}();
+                       $ok = $status->isGood();
+                       if ( !$ok ) {
+                               $this->parent->showStatusErrorBox( $status );
+                       }
+                       $this->endStage( $ok );
+               }
+               $this->addHTML("</ul>");
+               $this->endForm();
+               return true;
+
+       }
+
+       private function startStage( $msg ) {
+               $this->addHTML( "<li>" . wfMsgHtml( $msg ) . wfMsg( 'ellipsis') );
+       }
+
+       private function endStage( $success = true ) {
+               $msg = $success ? 'config-install-step-done' : 'config-install-step-failed';
+               $html = wfMsgHtml( 'word-separator' ) . wfMsgHtml( $msg );
+               if ( !$success ) {
+                       $html = "<span class=\"error\">$html</span>";
+               }
+               $this->addHTML( $html . "</li>\n" );
+       }
+}
+
+class WebInstaller_Complete extends WebInstallerPage {
+       public function execute() {
+               global $IP;
+               $this->startForm();
+               $msg = file_exists( "$IP/LocalSettings.php" ) ? 'config-install-done-moved' : 'config-install-done';
+               $this->addHTML(
+                       $this->parent->getInfoBox(
+                               wfMsgNoTrans( $msg,
+                                       $GLOBALS['wgServer'] .
+                                               $this->getVar( 'wgScriptPath' ) . '/index' .
+                                               $this->getVar( 'wgScriptExtension' )
+                               ), 'tick-32.png'
+                       )
+               );
+               $this->endForm( false );
+       }
+}
+
+class WebInstaller_Restart extends WebInstallerPage {
+       function execute() {
+               $r = $this->parent->request;
+               if ( $r->wasPosted() ) {
+                       $really = $r->getVal( 'submit-restart' );
+                       if ( $really ) {
+                               $this->parent->session = array();
+                               $this->parent->happyPages = array();
+                               $this->parent->settings = array();
+                       }
+                       return 'continue';
+               }
+
+               $this->startForm();
+               $s = $this->parent->getWarningBox( wfMsgNoTrans( 'config-help-restart' ) );
+               $this->addHTML( $s );
+               $this->endForm( 'restart' );
+       }
+}
+
+abstract class WebInstaller_Document extends WebInstallerPage {
+       abstract function getFileName();
+
+       function execute() {
+               $text = $this->getFileContents();
+               $this->parent->output->addWikiText( $text );
+               $this->startForm();
+               $this->endForm( false );
+       }
+
+       function getFileContents() {
+               return file_get_contents( dirname( __FILE__ ) . '/../../' . $this->getFileName() );
+       }
+
+       protected function formatTextFile( $text ) {
+               // replace numbering with [1], [2], etc with MW-style numbering
+               $text = preg_replace( "/\r?\n(\r?\n)?\\[\\d+\\]/m", "\\1#", $text );
+               // join word-wrapped lines into one
+               do {
+                       $prev = $text;
+                       $text = preg_replace( "/\n([\\*#])([^\r\n]*?)\r?\n([^\r\n#\\*:]+)/", "\n\\1\\2 \\3", $text );
+               } while ( $text != $prev );
+               // turn (bug nnnn) into links
+               $text = preg_replace_callback('/bug (\d+)/', array( $this, 'replaceBugLinks' ), $text );
+               // add links to manual to every global variable mentioned
+               $text = preg_replace_callback('/(\$wg[a-z0-9_]+)/i', array( $this, 'replaceConfigLinks' ), $text );
+               // special case for <pre> - formatted links
+               do {
+                       $prev = $text;
+                       $text = preg_replace( '/^([^\\s].*?)\r?\n[\\s]+(https?:\/\/)/m', "\\1\n:\\2", $text );
+               } while ( $text != $prev );
+               return $text;
+       }
+
+       private function replaceBugLinks( $matches ) {
+               return '<span class="config-plainlink">[https://bugzilla.wikimedia.org/' .
+                       $matches[1] . ' bug ' . $matches[1] . ']</span>';
+       }
+
+       private function replaceConfigLinks( $matches ) {
+               return '<span class="config-plainlink">[http://www.mediawiki.org/wiki/Manual:' .
+                       $matches[1] . ' ' . $matches[1] . ']</span>';
+       }
+}
+
+class WebInstaller_Readme extends WebInstaller_Document {
+       function getFileName() { return 'README'; }
+
+       function getFileContents() {
+               return $this->formatTextFile( parent::getFileContents() );
+       }
+}
+
+class WebInstaller_ReleaseNotes extends WebInstaller_Document {
+       function getFileName() { return 'RELEASE-NOTES'; }
+
+       function getFileContents() {
+               return $this->formatTextFile( parent::getFileContents() );
+       }
+}
+
+class WebInstaller_UpgradeDoc extends WebInstaller_Document {
+       function getFileName() { return 'UPGRADE'; }
+
+       function getFileContents() {
+               return $this->formatTextFile( parent::getFileContents() );
+       }
+}
+
+class WebInstaller_Copying extends WebInstaller_Document {
+       function getFileName() { return 'COPYING'; }
+
+       function getFileContents() {
+               $text = parent::getFileContents();
+               $text = str_replace( "\x0C", '', $text );
+               $text = preg_replace_callback( '/\n[ \t]+/m', array( 'WebInstaller_Copying', 'replaceLeadingSpaces' ), $text );
+               $text = '<tt>' . nl2br( $text ) . '</tt>';
+               return $text;
+       }
+
+       private static function replaceLeadingSpaces( $matches ) {
+               return "\n" . str_repeat( '&nbsp;', strlen( $matches[0] ) );
+       }
+}
diff --git a/includes/installer/WebInstallerOutput.php b/includes/installer/WebInstallerOutput.php
new file mode 100644 (file)
index 0000000..1b88a74
--- /dev/null
@@ -0,0 +1,197 @@
+<?php
+
+/**
+ * Output class modelled on OutputPage.
+ *
+ * I've opted to use a distinct class rather than derive from OutputPage here in 
+ * the interests of separation of concerns: if we used a subclass, there would be 
+ * quite a lot of things you could do in OutputPage that would break the installer, 
+ * that wouldn't be immediately obvious. 
+ */
+class WebInstallerOutput {
+       var $parent;
+       var $contents = '';
+       var $warnings = '';
+       var $headerDone = false;
+       var $redirectTarget;
+       var $debug = true;
+       var $useShortHeader = false;
+
+       function __construct( $parent ) {
+               $this->parent = $parent;
+       }
+
+       function addHTML( $html ) {
+               $this->contents .= $html;
+               $this->flush();
+       }
+
+       function addWikiText( $text ) {
+               $this->addHTML( $this->parent->parse( $text ) );
+       }
+
+       function addHTMLNoFlush( $html ) {
+               $this->contents .= $html;
+       }
+
+       function addWarning( $msg ) {
+               $this->warnings .= "<p>$msg</p>\n";
+       }
+       
+       function addWarningMsg( $msg /*, ... */ ) {
+               $params = func_get_args();
+               array_shift( $params );
+               $this->addWarning( wfMsg( $msg, $params ) );
+       }
+
+       function redirect( $url ) {
+               if ( $this->headerDone ) {
+                       throw new MWException( __METHOD__ . ' called after sending headers' );
+               }
+               $this->redirectTarget = $url;
+       }
+
+       function output() {
+               $this->flush();
+               $this->outputFooter();
+       }
+
+       function useShortHeader( $use = true ) {
+               $this->useShortHeader = $use;
+       }
+
+       function flush() {
+               if ( !$this->headerDone ) {
+                       $this->outputHeader();
+               }
+               if ( !$this->redirectTarget && strlen( $this->contents ) ) {
+                       echo $this->contents;
+                       flush();
+                       $this->contents = '';
+               }
+       }
+
+       function getDir() {
+               global $wgLang;
+               if( !is_object( $wgLang ) || !$wgLang->isRtl() )
+                       return 'ltr';
+               else
+                       return 'rtl';
+       }
+
+       function headerDone() {
+               return $this->headerDone;
+       }
+
+       function outputHeader() {
+               $this->headerDone = true;
+               $dbTypes = $this->parent->getDBTypes();
+
+               $this->parent->request->response()->header("Content-Type: text/html; charset=utf-8");
+               if ( $this->redirectTarget ) {
+                       $this->parent->request->response()->header( 'Location: '.$this->redirectTarget );
+                       return;
+               }
+
+               if ( $this->useShortHeader ) {
+                       $this->outputShortHeader();
+                       return;
+               }
+
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
+<head>
+       <meta name="robots" content="noindex, nofollow" />
+       <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+       <title><?php $this->outputTitle(); ?></title>
+       <link rel="stylesheet" type="text/css" href="../skins/common/shared.css"/>
+       <link rel="stylesheet" type="text/css" href="../skins/monobook/main.css"/>
+       <link rel="stylesheet" type="text/css" href="../skins/common/config.css"/>
+       <script type="text/javascript"><!--
+<?php echo "var dbTypes = " . Xml::encodeJsVar( $dbTypes ) . ";\n"; ?>
+       // -->
+       </script>
+       <script type="text/javascript" src="../skins/common/jquery.min.js"></script>
+       <script type="text/javascript" src="../skins/common/config.js"></script>
+</head>
+
+<body class="<?php print $this->getDir(); ?>">
+<noscript>
+<style type="text/css">
+.config-help-message { display: block; }
+.config-show-help { display: none; }
+</style>
+</noscript>
+<div id="globalWrapper">
+<div id="column-content">
+<div id="content">
+<div id="bodyContent">
+
+<h1><?php $this->outputTitle(); ?></h1>
+<?php
+       }
+
+       function outputFooter() {
+               $this->outputWarnings();
+
+               if ( $this->useShortHeader ) {
+?>
+</body></html>
+<?php
+                       return;
+               }
+?>
+
+</div></div></div>
+
+
+<div id="column-one">
+       <div class="portlet" id="p-logo">
+         <a style="background-image: url(../skins/common/images/mediawiki.png);"
+           href="http://www.mediawiki.org/"
+           title="Main Page"></a>
+       </div>
+       <script type="text/javascript"> if (window.isMSIE55) fixalpha(); </script>
+       <div class='portlet'><div class='pBody'>
+<?php
+       echo $this->parent->parse( wfMsgNoTrans( 'config-sidebar' ), true );
+?>
+       </div></div>
+</div>
+
+</div>
+
+</body>
+</html>
+<?php
+       }
+
+       function outputShortHeader() {
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
+<head>
+       <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+       <meta name="robots" content="noindex, nofollow" />
+       <title><?php $this->outputTitle(); ?></title>
+       <link rel="stylesheet" type="text/css" href="../skins/monobook/main.css"/>
+       <link rel="stylesheet" type="text/css" href="../skins/common/config.css"/>
+       <script type="text/javascript" src="../skins/common/jquery.min.js"></script>
+       <script type="text/javascript" src="../skins/common/config.js"></script>
+</head>
+
+<body style="background-image: none">
+<?php
+       }
+
+       function outputTitle() {
+               global $wgVersion;
+               echo htmlspecialchars( wfMsg( 'config-title', $wgVersion ) );
+       }
+
+       function outputWarnings() {
+               $this->addHTML( $this->warnings );
+               $this->warnings = '';
+       }
+}
diff --git a/maintenance/install.php b/maintenance/install.php
new file mode 100644 (file)
index 0000000..96afcf7
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+
+if ( php_sapi_name() != 'cli' ) {
+       echo "This is a command-line script.\n";
+       exit( 1 );
+}
+
+define( 'MEDIAWIKI', 1 );
+define( 'MW_NO_DB', 1 );
+define( 'MW_NO_SESSION', 1 );
+define( 'MW_CONFIG_CALLBACK', 'wfInstallerConfig' );
+
+$IP = dirname( dirname( __FILE__ ) );
+
+function wfInstallerConfig() {
+       // Don't access the database
+       $GLOBALS['wgUseDatabaseMessages'] = false;
+       // Debug-friendly
+       $GLOBALS['wgShowExceptionDetails'] = true;
+       // Don't break forms
+       $GLOBALS['wgExternalLinkTarget'] = '_blank';
+}
+
+require_once( "$IP/includes/ProfilerStub.php" );
+require_once( "$IP/includes/Defines.php" );
+require_once( "$IP/includes/GlobalFunctions.php" );
+require_once( "$IP/includes/AutoLoader.php" );
+require_once( "$IP/includes/Hooks.php" );
+require_once( "$IP/includes/DefaultSettings.php" );
+require_once( "$IP/includes/Namespace.php" );
+
+$wgContLang = Language::factory( 'en' ); // will be overridden later
+
+// Disable the i18n cache and LoadBalancer
+Language::getLocalisationCache()->disableBackend();
+LBFactory::disableBackend();
+
+$installer = new CliInstaller( $argv );
+
+$langCode = 'en';
+
+$wgLang = Language::factory( $langCode );
+
+$wgMetaNamespace = $wgCanonicalNamespaceNames[NS_PROJECT];
+
+$session = $installer->execute( $argv );
+
+$_SESSION['installData'] = $session;
+
diff --git a/skins/common/config-cc.css b/skins/common/config-cc.css
new file mode 100644 (file)
index 0000000..c629ad3
--- /dev/null
@@ -0,0 +1,56 @@
+/**
+ * Copy of CC standard stylesheet, plus tweaks for iframe usage
+ */
+
+body {
+       margin:0px;
+       background:#eee;
+       font-family:verdana;
+       color:#333;
+}
+
+#main {
+       border:1px solid #D0D0D0; 
+       background:#fff;
+       margin: 0.5em;
+}
+
+/* Looks like you have to specify the width of #menu
+or IE5 Mac stretches it all the way across the div, and 
+Opera streches it half way. */
+
+#main #menu {
+       border-left:1px dotted #ccc; 
+       /*              border-bottom:1px solid #000;*/
+       float:right;
+       width:230px;
+       background:white;
+       margin:0px 0px 10px 10px;
+}
+
+td, h3, p,h1,pre {
+       margin:0px 20px 20px 20px;
+       font-size:11px;
+       line-height:140%;
+}
+
+.header {
+       padding-left: 10px;
+       padding-top:10px;
+}
+
+.nav {
+       padding-left:10px;
+       padding-bottom:10px;
+       font-size:11px;
+       margin-bottom:16px;
+}
+
+#menu p { 
+       font-size:11px;
+}
+
+.dent {
+       margin-left:64px;
+}
+
diff --git a/skins/common/config.css b/skins/common/config.css
new file mode 100644 (file)
index 0000000..a7be5e3
--- /dev/null
@@ -0,0 +1,159 @@
+.env-check {
+       font-size: 90%;
+       margin: 1em 0 1em 2.5em;
+}
+
+.config-section {
+       margin-top: 2em;
+}
+
+.config-label {
+       clear: left;
+       font-weight: bold;
+       width: 10em;
+       float: left;
+       text-align: right;
+       padding-right: 1em;
+       padding-top: .2em;
+}
+
+.config-input {
+       clear: left;
+       zoom: 100%; /* IE hack */
+}
+
+.config-page-wrapper {
+       padding: 0.5em;
+}
+
+.config-page-list {
+       float: right;
+       width: 12em;
+       border: 1px solid #aaa;
+       padding: 0.5em;
+       margin: 0.5em;
+}
+
+.config-page {
+       padding: 0.5em 2em 0.5em 2em;
+       /* 15em right margin to leave space for 12em page list */
+       margin: 0.5em 15em 0.5em 0.5em;
+       border: 1px solid #aaa;
+}
+
+.config-submit {
+       clear: left;
+       text-align: center;
+       padding: 1em;
+}
+
+.config-submit input {
+       margin-left: 0.5em;
+       margin-right: 0.5em;
+}
+
+.config-page-disabled {
+       color: #aaa;
+}
+
+.config-error-box {
+       border: 2px solid #f00;
+       margin: 0.4em;
+       clear: left;
+}
+
+.config-info-left {
+       margin: 7px;
+       float: left;
+       width: 35px;
+}
+
+.config-info-right {
+       margin: 0.5em 0.5em 0.5em 49px;
+       width: 30em;
+}
+
+.config-page-current {
+       font-weight: bold;
+}
+
+.config-help-wrapper {
+       clear: left;
+       margin: 0 0 2em 12em;
+       padding-top: 1em;
+}
+.config-show-help, .config-hide-help {
+       margin-left: 14em;
+}
+.config-hide-help { display: none; }
+.config-show-help a, .config-hide-help a {
+       background-image: url(images/help-22.png);
+       background-position: 0 0;
+       background-repeat: no-repeat;
+       display: inline-block;
+       height: 26px;
+       padding-left: 26px;
+}
+
+.config-help-message {
+       font-size: 85%;
+       display: none;
+}
+
+.config-message {
+       display: list-item;
+       line-height: 1.5em;
+       list-style-image: url(../skins/common/images/bullet.gif);
+       list-style-type: square;
+}
+
+.config-input-text {
+       width: 20em;
+       margin-right: 1em;
+}
+
+.config-input-check {
+       margin-left: 10em;
+}
+
+.error {
+       color: red;
+       background-color: #fff;
+       font-weight: bold;
+       left: 1em;
+       font-size: 100%;
+}
+
+.config-settings-block {
+       list-style-type: none;
+       list-style-image: none;
+       float: left;
+       margin: 0;
+       padding: 0;
+}
+
+.btn-install {
+       font-weight: bold;
+       font-size: 110%;
+       padding: .2em .3em;
+}
+
+.success-message {
+       font-weight: bold;
+       font-size: 110%;
+       color: green;
+}
+.success-box {
+       font-size: 130%;
+}
+
+.config-cc-wrapper {
+       clear: left;
+       /* If you change this height, also change it in WebInstaller_Options::submitCC() */
+       height: 54em; 
+}
+
+.config-plainlink a {
+       background: none !important;
+       padding: 0 !important;
+}
diff --git a/skins/common/config.js b/skins/common/config.js
new file mode 100644 (file)
index 0000000..19f23d0
--- /dev/null
@@ -0,0 +1,88 @@
+(function( $ ) {
+       $( document ).ready( function() {
+               // Show/hide code for help text
+               $( '.config-show-help a' ).click( function() {
+                       $(this).parent().siblings( '.config-help-message' ).show( 'slow' );
+                       $(this).parent().siblings( '.config-hide-help' ).show();
+                       $(this).parent().hide();
+                       return false;
+               } );
+               $( '.config-hide-help a' ).click( function() {
+                       $(this).parent().siblings( '.config-help-message' ).hide( 'slow' );
+                       $(this).parent().siblings( '.config-show-help' ).show();
+                       $(this).parent().hide();
+                       return false;
+               } );
+               
+               // Show/hide code for DB-specific options
+               // FIXME: Do we want slow, fast, or even non-animated (instantaneous) showing/hiding here?
+               $( '.dbRadio' ).each( function() { $( '#' + $(this).attr( 'rel' ) ).hide(); } );
+               $( '#' + $( '.dbRadio:checked' ).attr( 'rel' ) ).show();
+               $( '.dbRadio' ).click( function() {
+                       var $checked = $( '.dbRadio:checked' );
+                       var $wrapper = $( '#' + $checked.attr( 'rel' ) );
+                       if ( !$wrapper.is( ':visible' ) ) {
+                               $( '.dbWrapper' ).hide( 'slow' );
+                               $wrapper.show( 'slow' );
+                       }
+               } );
+               
+               // Scroll to the bottom of upgrade log
+               $( "#config-update-log" ).each( function() { this.scrollTop = this.scrollHeight; } );
+               
+               // Show/hide Creative Commons thingy
+               $( '.licenseRadio' ).click( function() {
+                       var $wrapper = $( '#config-cc-wrapper' );
+                       if ( $( '#config__LicenseCode_cc-choose' ).is( ':checked' ) ) {
+                               $wrapper.show( 'slow' );
+                       } else {
+                               $wrapper.hide( 'slow' );
+                       }
+               } );
+               
+               // Show/hide random stuff (email, upload)
+               $( '.showHideRadio' ).click( function() {
+                       var $wrapper = $( '#' + $(this).attr( 'rel' ) );
+                       if ( $(this).is( ':checked' ) ) {
+                               $wrapper.show( 'slow' );
+                       } else {
+                               $wrapper.hide( 'slow' );
+                       }
+               } );
+               $( '.hideShowRadio' ).click( function() {
+                       var $wrapper = $( '#' + $(this).attr( 'rel' ) );
+                       if ( $(this).is( ':checked' ) ) {
+                               $wrapper.hide( 'slow' );
+                       } else {
+                               $wrapper.show( 'slow' );
+                       }
+               } );
+               
+               // Enable/disable "other" textboxes
+               $( '.enableForOther' ).click( function() {
+                       var $textbox = $( '#' + $(this).attr( 'rel' ) );
+                       if ( $(this).val() == 'other' ) { // FIXME: Ugh, this is ugly
+                               $textbox.removeAttr( 'disabled' );
+                       } else {
+                               $textbox.attr( 'disabled', 'disabled' );
+                       }
+               } );
+               
+               // Synchronize radio button label for sitename with textbox
+               $label = $( 'label[for=config__NamespaceType_site-name]' );
+               labelText = $label.text();
+               $label.text( labelText.replace( '$1', '' ) );
+               $( '#config_wgSitename' ).bind( 'keyup change', syncText ).each( syncText );
+               function syncText() {
+                       var value = $(this).val()
+                               .replace( /[\[\]\{\}|#<>%+? ]/g, '_' )
+                               .replace( /&/, '&amp;' )
+                               .replace( /__+/g, '_' )
+                               .replace( /^_+/, '' )
+                               .replace( /_+$/, '' );
+                       value = value.substr( 0, 1 ).toUpperCase() + value.substr( 1 );
+                       $label.text( labelText.replace( '$1', value ) );
+               }
+               
+       } );
+})(jQuery);
diff --git a/skins/common/images/critical-32.png b/skins/common/images/critical-32.png
new file mode 100644 (file)
index 0000000..501424b
Binary files /dev/null and b/skins/common/images/critical-32.png differ
diff --git a/skins/common/images/help-22.png b/skins/common/images/help-22.png
new file mode 100644 (file)
index 0000000..273a49c
Binary files /dev/null and b/skins/common/images/help-22.png differ
diff --git a/skins/common/images/info-32.png b/skins/common/images/info-32.png
new file mode 100644 (file)
index 0000000..f59130d
Binary files /dev/null and b/skins/common/images/info-32.png differ
diff --git a/skins/common/images/tick-32.png b/skins/common/images/tick-32.png
new file mode 100644 (file)
index 0000000..ab86df2
Binary files /dev/null and b/skins/common/images/tick-32.png differ
diff --git a/skins/common/images/warning-32.png b/skins/common/images/warning-32.png
new file mode 100644 (file)
index 0000000..704cf4b
Binary files /dev/null and b/skins/common/images/warning-32.png differ