]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/installer/Installer.php
MediaWiki 1.17.0
[autoinstalls/mediawiki.git] / includes / installer / Installer.php
1 <?php
2 /**
3  * Base code for MediaWiki installer.
4  *
5  * @file
6  * @ingroup Deployment
7  */
8
9 /**
10  * This documentation group collects source code files with deployment functionality.
11  *
12  * @defgroup Deployment Deployment
13  */
14
15 /**
16  * Base installer class.
17  *
18  * This class provides the base for installation and update functionality
19  * for both MediaWiki core and extensions.
20  *
21  * @ingroup Deployment
22  * @since 1.17
23  */
24 abstract class Installer {
25
26         // This is the absolute minimum PHP version we can support
27         const MINIMUM_PHP_VERSION = '5.2.3';
28
29         /**
30          * @var array
31          */
32         protected $settings;
33
34         /**
35          * Cached DB installer instances, access using getDBInstaller().
36          *
37          * @var array
38          */
39         protected $dbInstallers = array();
40
41         /**
42          * Minimum memory size in MB.
43          *
44          * @var integer
45          */
46         protected $minMemorySize = 50;
47
48         /**
49          * Cached Title, used by parse().
50          *
51          * @var Title
52          */
53         protected $parserTitle;
54
55         /**
56          * Cached ParserOptions, used by parse().
57          *
58          * @var ParserOptions
59          */
60         protected $parserOptions;
61
62         /**
63          * Known database types. These correspond to the class names <type>Installer,
64          * and are also MediaWiki database types valid for $wgDBtype.
65          *
66          * To add a new type, create a <type>Installer class and a Database<type>
67          * class, and add a config-type-<type> message to MessagesEn.php.
68          *
69          * @var array
70          */
71         protected static $dbTypes = array(
72                 'mysql',
73                 'postgres',
74                 'oracle',
75                 'sqlite',
76         );
77
78         /**
79          * A list of environment check methods called by doEnvironmentChecks().
80          * These may output warnings using showMessage(), and/or abort the
81          * installation process by returning false.
82          *
83          * @var array
84          */
85         protected $envChecks = array(
86                 'envCheckDB',
87                 'envCheckRegisterGlobals',
88                 'envCheckBrokenXML',
89                 'envCheckPHP531',
90                 'envCheckMagicQuotes',
91                 'envCheckMagicSybase',
92                 'envCheckMbstring',
93                 'envCheckZE1',
94                 'envCheckSafeMode',
95                 'envCheckXML',
96                 'envCheckPCRE',
97                 'envCheckMemory',
98                 'envCheckCache',
99                 'envCheckDiff3',
100                 'envCheckGraphics',
101                 'envCheckPath',
102                 'envCheckExtension',
103                 'envCheckShellLocale',
104                 'envCheckUploadsDirectory',
105                 'envCheckLibicu',
106                 'envCheckSuhosinMaxValueLength',
107         );
108
109         /**
110          * MediaWiki configuration globals that will eventually be passed through
111          * to LocalSettings.php. The names only are given here, the defaults
112          * typically come from DefaultSettings.php.
113          *
114          * @var array
115          */
116         protected $defaultVarNames = array(
117                 'wgSitename',
118                 'wgPasswordSender',
119                 'wgLanguageCode',
120                 'wgRightsIcon',
121                 'wgRightsText',
122                 'wgRightsUrl',
123                 'wgMainCacheType',
124                 'wgEnableEmail',
125                 'wgEnableUserEmail',
126                 'wgEnotifUserTalk',
127                 'wgEnotifWatchlist',
128                 'wgEmailAuthentication',
129                 'wgDBtype',
130                 'wgDiff3',
131                 'wgImageMagickConvertCommand',
132                 'IP',
133                 'wgScriptPath',
134                 'wgScriptExtension',
135                 'wgMetaNamespace',
136                 'wgDeletedDirectory',
137                 'wgEnableUploads',
138                 'wgLogo',
139                 'wgShellLocale',
140                 'wgSecretKey',
141                 'wgUseInstantCommons',
142                 'wgUpgradeKey',
143                 'wgDefaultSkin',
144                 'wgResourceLoaderMaxQueryLength',
145         );
146
147         /**
148          * Variables that are stored alongside globals, and are used for any
149          * configuration of the installation process aside from the MediaWiki
150          * configuration. Map of names to defaults.
151          *
152          * @var array
153          */
154         protected $internalDefaults = array(
155                 '_UserLang' => 'en',
156                 '_Environment' => false,
157                 '_CompiledDBs' => array(),
158                 '_SafeMode' => false,
159                 '_RaiseMemory' => false,
160                 '_UpgradeDone' => false,
161                 '_InstallDone' => false,
162                 '_Caches' => array(),
163                 '_InstallPassword' => '',
164                 '_SameAccount' => true,
165                 '_CreateDBAccount' => false,
166                 '_NamespaceType' => 'site-name',
167                 '_AdminName' => '', // will be set later, when the user selects language
168                 '_AdminPassword' => '',
169                 '_AdminPassword2' => '',
170                 '_AdminEmail' => '',
171                 '_Subscribe' => false,
172                 '_SkipOptional' => 'continue',
173                 '_RightsProfile' => 'wiki',
174                 '_LicenseCode' => 'none',
175                 '_CCDone' => false,
176                 '_Extensions' => array(),
177                 '_MemCachedServers' => '',
178                 '_UpgradeKeySupplied' => false,
179                 '_ExistingDBSettings' => false,
180         );
181
182         /**
183          * The actual list of installation steps. This will be initialized by getInstallSteps()
184          *
185          * @var array
186          */
187         private $installSteps = array();
188
189         /**
190          * Extra steps for installation, for things like DatabaseInstallers to modify
191          *
192          * @var array
193          */
194         protected $extraInstallSteps = array();
195
196         /**
197          * Known object cache types and the functions used to test for their existence.
198          *
199          * @var array
200          */
201         protected $objectCaches = array(
202                 'xcache' => 'xcache_get',
203                 'apc' => 'apc_fetch',
204                 'eaccel' => 'eaccelerator_get',
205                 'wincache' => 'wincache_ucache_get'
206         );
207
208         /**
209          * User rights profiles.
210          *
211          * @var array
212          */
213         public $rightsProfiles = array(
214                 'wiki' => array(),
215                 'no-anon' => array(
216                         '*' => array( 'edit' => false )
217                 ),
218                 'fishbowl' => array(
219                         '*' => array(
220                                 'createaccount' => false,
221                                 'edit' => false,
222                         ),
223                 ),
224                 'private' => array(
225                         '*' => array(
226                                 'createaccount' => false,
227                                 'edit' => false,
228                                 'read' => false,
229                         ),
230                 ),
231         );
232
233         /**
234          * License types.
235          *
236          * @var array
237          */
238         public $licenses = array(
239                 'cc-by-sa' => array(
240                         'url' => 'http://creativecommons.org/licenses/by-sa/3.0/',
241                         'icon' => '{$wgStylePath}/common/images/cc-by-sa.png',
242                 ),
243                 'cc-by-nc-sa' => array(
244                         'url' => 'http://creativecommons.org/licenses/by-nc-sa/3.0/',
245                         'icon' => '{$wgStylePath}/common/images/cc-by-nc-sa.png',
246                 ),
247                 'cc-0' => array(
248                         'url' => 'https://creativecommons.org/publicdomain/zero/1.0/',
249                         'icon' => '{$wgStylePath}/common/images/cc-0.png',
250                 ),
251                 'pd' => array(
252                         'url' => 'http://creativecommons.org/licenses/publicdomain/',
253                         'icon' => '{$wgStylePath}/common/images/public-domain.png',
254                 ),
255                 'gfdl-old' => array(
256                         'url' => 'http://www.gnu.org/licenses/old-licenses/fdl-1.2.html',
257                         'icon' => '{$wgStylePath}/common/images/gnu-fdl.png',
258                 ),
259                 'gfdl-current' => array(
260                         'url' => 'http://www.gnu.org/copyleft/fdl.html',
261                         'icon' => '{$wgStylePath}/common/images/gnu-fdl.png',
262                 ),
263                 'none' => array(
264                         'url' => '',
265                         'icon' => '',
266                         'text' => ''
267                 ),
268                 'cc-choose' => array(
269                         // Details will be filled in by the selector.
270                         'url' => '',
271                         'icon' => '',
272                         'text' => '',
273                 ),
274         );
275
276         /**
277          * URL to mediawiki-announce subscription
278          */
279         protected $mediaWikiAnnounceUrl = 'https://lists.wikimedia.org/mailman/subscribe/mediawiki-announce';
280
281         /**
282          * Supported language codes for Mailman
283          */
284         protected $mediaWikiAnnounceLanguages = array(
285                 'ca', 'cs', 'da', 'de', 'en', 'es', 'et', 'eu', 'fi', 'fr', 'hr', 'hu',
286                 'it', 'ja', 'ko', 'lt', 'nl', 'no', 'pl', 'pt', 'pt-br', 'ro', 'ru',
287                 'sl', 'sr', 'sv', 'tr', 'uk'
288         );
289
290         /**
291          * UI interface for displaying a short message
292          * The parameters are like parameters to wfMsg().
293          * The messages will be in wikitext format, which will be converted to an
294          * output format such as HTML or text before being sent to the user.
295          */
296         public abstract function showMessage( $msg /*, ... */ );
297
298         /**
299          * Same as showMessage(), but for displaying errors
300          */
301         public abstract function showError( $msg /*, ... */ );
302
303         /**
304          * Show a message to the installing user by using a Status object
305          * @param $status Status
306          */
307         public abstract function showStatusMessage( Status $status );
308
309         /**
310          * Constructor, always call this from child classes.
311          */
312         public function __construct() {
313                 global $wgExtensionMessagesFiles, $wgUser, $wgHooks;
314
315                 // Disable the i18n cache and LoadBalancer
316                 Language::getLocalisationCache()->disableBackend();
317                 LBFactory::disableBackend();
318
319                 // Load the installer's i18n file.
320                 $wgExtensionMessagesFiles['MediawikiInstaller'] =
321                         dirname( __FILE__ ) . '/Installer.i18n.php';
322
323                 // Having a user with id = 0 safeguards us from DB access via User::loadOptions().
324                 $wgUser = User::newFromId( 0 );
325
326                 $this->settings = $this->internalDefaults;
327
328                 foreach ( $this->defaultVarNames as $var ) {
329                         $this->settings[$var] = $GLOBALS[$var];
330                 }
331
332                 foreach ( self::getDBTypes() as $type ) {
333                         $installer = $this->getDBInstaller( $type );
334
335                         if ( !$installer->isCompiled() ) {
336                                 continue;
337                         }
338
339                         $defaults = $installer->getGlobalDefaults();
340
341                         foreach ( $installer->getGlobalNames() as $var ) {
342                                 if ( isset( $defaults[$var] ) ) {
343                                         $this->settings[$var] = $defaults[$var];
344                                 } else {
345                                         $this->settings[$var] = $GLOBALS[$var];
346                                 }
347                         }
348                 }
349
350                 $this->parserTitle = Title::newFromText( 'Installer' );
351                 $this->parserOptions = new ParserOptions; // language will  be wrong :(
352                 $this->parserOptions->setEditSection( false );
353         }
354
355         /**
356          * Get a list of known DB types.
357          */
358         public static function getDBTypes() {
359                 return self::$dbTypes;
360         }
361
362         /**
363          * Do initial checks of the PHP environment. Set variables according to
364          * the observed environment.
365          *
366          * It's possible that this may be called under the CLI SAPI, not the SAPI
367          * that the wiki will primarily run under. In that case, the subclass should
368          * initialise variables such as wgScriptPath, before calling this function.
369          *
370          * Under the web subclass, it can already be assumed that PHP 5+ is in use
371          * and that sessions are working.
372          *
373          * @return Status
374          */
375         public function doEnvironmentChecks() {
376                 $phpVersion = phpversion();
377                 if( version_compare( $phpVersion, self::MINIMUM_PHP_VERSION, '>=' ) ) {
378                         $this->showMessage( 'config-env-php', $phpVersion );
379                         $good = true;
380                 } else {
381                         $this->showMessage( 'config-env-php-toolow', $phpVersion, self::MINIMUM_PHP_VERSION );
382                         $good = false;
383                 }
384
385                 if( $good ) {
386                         foreach ( $this->envChecks as $check ) {
387                                 $status = $this->$check();
388                                 if ( $status === false ) {
389                                         $good = false;
390                                 }
391                         }
392                 }
393
394                 $this->setVar( '_Environment', $good );
395
396                 return $good ? Status::newGood() : Status::newFatal( 'config-env-bad' );
397         }
398
399         /**
400          * Set a MW configuration variable, or internal installer configuration variable.
401          *
402          * @param $name String
403          * @param $value Mixed
404          */
405         public function setVar( $name, $value ) {
406                 $this->settings[$name] = $value;
407         }
408
409         /**
410          * Get an MW configuration variable, or internal installer configuration variable.
411          * The defaults come from $GLOBALS (ultimately DefaultSettings.php).
412          * Installer variables are typically prefixed by an underscore.
413          *
414          * @param $name String
415          * @param $default Mixed
416          *
417          * @return mixed
418          */
419         public function getVar( $name, $default = null ) {
420                 if ( !isset( $this->settings[$name] ) ) {
421                         return $default;
422                 } else {
423                         return $this->settings[$name];
424                 }
425         }
426
427         /**
428          * Get an instance of DatabaseInstaller for the specified DB type.
429          *
430          * @param $type Mixed: DB installer for which is needed, false to use default.
431          *
432          * @return DatabaseInstaller
433          */
434         public function getDBInstaller( $type = false ) {
435                 if ( !$type ) {
436                         $type = $this->getVar( 'wgDBtype' );
437                 }
438
439                 $type = strtolower( $type );
440
441                 if ( !isset( $this->dbInstallers[$type] ) ) {
442                         $class = ucfirst( $type ). 'Installer';
443                         $this->dbInstallers[$type] = new $class( $this );
444                 }
445
446                 return $this->dbInstallers[$type];
447         }
448
449         /**
450          * Determine if LocalSettings.php exists. If it does, return its variables,
451          * merged with those from AdminSettings.php, as an array.
452          *
453          * @return Array
454          */
455         public static function getExistingLocalSettings() {
456                 global $IP;
457
458                 wfSuppressWarnings();
459                 $_lsExists = file_exists( "$IP/LocalSettings.php" );
460                 wfRestoreWarnings();
461
462                 if( !$_lsExists ) {
463                         return false;
464                 }
465                 unset($_lsExists);
466
467                 require( "$IP/includes/DefaultSettings.php" );
468                 require( "$IP/LocalSettings.php" );
469                 if ( file_exists( "$IP/AdminSettings.php" ) ) {
470                         require( "$IP/AdminSettings.php" );
471                 }
472                 return get_defined_vars();
473         }
474
475         /**
476          * Get a fake password for sending back to the user in HTML.
477          * This is a security mechanism to avoid compromise of the password in the
478          * event of session ID compromise.
479          *
480          * @param $realPassword String
481          *
482          * @return string
483          */
484         public function getFakePassword( $realPassword ) {
485                 return str_repeat( '*', strlen( $realPassword ) );
486         }
487
488         /**
489          * Set a variable which stores a password, except if the new value is a
490          * fake password in which case leave it as it is.
491          *
492          * @param $name String
493          * @param $value Mixed
494          */
495         public function setPassword( $name, $value ) {
496                 if ( !preg_match( '/^\*+$/', $value ) ) {
497                         $this->setVar( $name, $value );
498                 }
499         }
500
501         /**
502          * On POSIX systems return the primary group of the webserver we're running under.
503          * On other systems just returns null.
504          *
505          * This is used to advice the user that he should chgrp his mw-config/data/images directory as the
506          * webserver user before he can install.
507          *
508          * Public because SqliteInstaller needs it, and doesn't subclass Installer.
509          *
510          * @return mixed
511          */
512         public static function maybeGetWebserverPrimaryGroup() {
513                 if ( !function_exists( 'posix_getegid' ) || !function_exists( 'posix_getpwuid' ) ) {
514                         # I don't know this, this isn't UNIX.
515                         return null;
516                 }
517
518                 # posix_getegid() *not* getmygid() because we want the group of the webserver,
519                 # not whoever owns the current script.
520                 $gid = posix_getegid();
521                 $getpwuid = posix_getpwuid( $gid );
522                 $group = $getpwuid['name'];
523
524                 return $group;
525         }
526
527         /**
528          * Convert wikitext $text to HTML.
529          *
530          * This is potentially error prone since many parser features require a complete
531          * installed MW database. The solution is to just not use those features when you
532          * write your messages. This appears to work well enough. Basic formatting and
533          * external links work just fine.
534          *
535          * But in case a translator decides to throw in a #ifexist or internal link or
536          * whatever, this function is guarded to catch the attempted DB access and to present
537          * some fallback text.
538          *
539          * @param $text String
540          * @param $lineStart Boolean
541          * @return String
542          */
543         public function parse( $text, $lineStart = false ) {
544                 global $wgParser;
545
546                 try {
547                         $out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
548                         $html = $out->getText();
549                 } catch ( DBAccessError $e ) {
550                         $html = '<!--DB access attempted during parse-->  ' . htmlspecialchars( $text );
551
552                         if ( !empty( $this->debug ) ) {
553                                 $html .= "<!--\n" . $e->getTraceAsString() . "\n-->";
554                         }
555                 }
556
557                 return $html;
558         }
559
560         public function getParserOptions() {
561                 return $this->parserOptions;
562         }
563
564         public function disableLinkPopups() {
565                 $this->parserOptions->setExternalLinkTarget( false );
566         }
567
568         public function restoreLinkPopups() {
569                 global $wgExternalLinkTarget;
570                 $this->parserOptions->setExternalLinkTarget( $wgExternalLinkTarget );
571         }
572
573         /**
574          * Install step which adds a row to the site_stats table with appropriate
575          * initial values.
576          */
577         public function populateSiteStats( DatabaseInstaller $installer ) {
578                 $status = $installer->getConnection();
579                 if ( !$status->isOK() ) {
580                         return $status;
581                 }
582                 $status->value->insert( 'site_stats', array(
583                         'ss_row_id' => 1,
584                         'ss_total_views' => 0,
585                         'ss_total_edits' => 0,
586                         'ss_good_articles' => 0,
587                         'ss_total_pages' => 0,
588                         'ss_users' => 0,
589                         'ss_admins' => 0,
590                         'ss_images' => 0 ),
591                         __METHOD__, 'IGNORE' );
592                 return Status::newGood();
593         }
594
595         /**
596          * Exports all wg* variables stored by the installer into global scope.
597          */
598         public function exportVars() {
599                 foreach ( $this->settings as $name => $value ) {
600                         if ( substr( $name, 0, 2 ) == 'wg' ) {
601                                 $GLOBALS[$name] = $value;
602                         }
603                 }
604         }
605
606         /**
607          * Environment check for DB types.
608          */
609         protected function envCheckDB() {
610                 global $wgLang;
611
612                 $compiledDBs = array();
613                 $allNames = array();
614
615                 foreach ( self::getDBTypes() as $name ) {
616                         $db = $this->getDBInstaller( $name );
617                         $readableName = wfMsg( 'config-type-' . $name );
618
619                         if ( $db->isCompiled() ) {
620                                 $compiledDBs[] = $name;
621                         }
622                         $allNames[] = $readableName;
623                 }
624
625                 $this->setVar( '_CompiledDBs', $compiledDBs );
626
627                 if ( !$compiledDBs ) {
628                         $this->showError( 'config-no-db', $wgLang->commaList( $allNames ) );
629                         // FIXME: this only works for the web installer!
630                         return false;
631                 }
632
633                 // Check for FTS3 full-text search module
634                 $sqlite = $this->getDBInstaller( 'sqlite' );
635                 if ( $sqlite->isCompiled() ) {
636                         if( DatabaseSqlite::getFulltextSearchModule() != 'FTS3' ) {
637                                 $this->showMessage( 'config-no-fts3' );
638                         }
639                 }
640         }
641
642         /**
643          * Environment check for register_globals.
644          */
645         protected function envCheckRegisterGlobals() {
646                 if( wfIniGetBool( "magic_quotes_runtime" ) ) {
647                         $this->showMessage( 'config-register-globals' );
648                 }
649         }
650
651         /**
652          * Some versions of libxml+PHP break < and > encoding horribly
653          */
654         protected function envCheckBrokenXML() {
655                 $test = new PhpXmlBugTester();
656                 if ( !$test->ok ) {
657                         $this->showError( 'config-brokenlibxml' );
658                         return false;
659                 }
660         }
661
662         /**
663          * Test PHP (probably 5.3.1, but it could regress again) to make sure that
664          * reference parameters to __call() are not converted to null
665          */
666         protected function envCheckPHP531() {
667                 $test = new PhpRefCallBugTester;
668                 $test->execute();
669                 if ( !$test->ok ) {
670                         $this->showError( 'config-using531', phpversion() );
671                         return false;
672                 }
673         }
674
675         /**
676          * Environment check for magic_quotes_runtime.
677          */
678         protected function envCheckMagicQuotes() {
679                 if( wfIniGetBool( "magic_quotes_runtime" ) ) {
680                         $this->showError( 'config-magic-quotes-runtime' );
681                         return false;
682                 }
683         }
684
685         /**
686          * Environment check for magic_quotes_sybase.
687          */
688         protected function envCheckMagicSybase() {
689                 if ( wfIniGetBool( 'magic_quotes_sybase' ) ) {
690                         $this->showError( 'config-magic-quotes-sybase' );
691                         return false;
692                 }
693         }
694
695         /**
696          * Environment check for mbstring.func_overload.
697          */
698         protected function envCheckMbstring() {
699                 if ( wfIniGetBool( 'mbstring.func_overload' ) ) {
700                         $this->showError( 'config-mbstring' );
701                         return false;
702                 }
703         }
704
705         /**
706          * Environment check for zend.ze1_compatibility_mode.
707          */
708         protected function envCheckZE1() {
709                 if ( wfIniGetBool( 'zend.ze1_compatibility_mode' ) ) {
710                         $this->showError( 'config-ze1' );
711                         return false;
712                 }
713         }
714
715         /**
716          * Environment check for safe_mode.
717          */
718         protected function envCheckSafeMode() {
719                 if ( wfIniGetBool( 'safe_mode' ) ) {
720                         $this->setVar( '_SafeMode', true );
721                         $this->showMessage( 'config-safe-mode' );
722                 }
723         }
724
725         /**
726          * Environment check for the XML module.
727          */
728         protected function envCheckXML() {
729                 if ( !function_exists( "utf8_encode" ) ) {
730                         $this->showError( 'config-xml-bad' );
731                         return false;
732                 }
733         }
734
735         /**
736          * Environment check for the PCRE module.
737          */
738         protected function envCheckPCRE() {
739                 if ( !function_exists( 'preg_match' ) ) {
740                         $this->showError( 'config-pcre' );
741                         return false;
742                 }
743                 wfSuppressWarnings();
744                 $regexd = preg_replace( '/[\x{0430}-\x{04FF}]/iu', '', '-АБВГД-' );
745                 wfRestoreWarnings();
746                 if ( $regexd != '--' ) {
747                         $this->showError( 'config-pcre-no-utf8' );
748                         return false;
749                 }
750         }
751
752         /**
753          * Environment check for available memory.
754          */
755         protected function envCheckMemory() {
756                 $limit = ini_get( 'memory_limit' );
757
758                 if ( !$limit || $limit == -1 ) {
759                         return true;
760                 }
761
762                 $n = wfShorthandToInteger( $limit );
763
764                 if( $n < $this->minMemorySize * 1024 * 1024 ) {
765                         $newLimit = "{$this->minMemorySize}M";
766
767                         if( ini_set( "memory_limit", $newLimit ) === false ) {
768                                 $this->showMessage( 'config-memory-bad', $limit );
769                         } else {
770                                 $this->showMessage( 'config-memory-raised', $limit, $newLimit );
771                                 $this->setVar( '_RaiseMemory', true );
772                         }
773                 } else {
774                         return true;
775                 }
776         }
777
778         /**
779          * Environment check for compiled object cache types.
780          */
781         protected function envCheckCache() {
782                 $caches = array();
783                 foreach ( $this->objectCaches as $name => $function ) {
784                         if ( function_exists( $function ) ) {
785                                 $caches[$name] = true;
786                         }
787                 }
788
789                 if ( !$caches ) {
790                         $this->showMessage( 'config-no-cache' );
791                 }
792
793                 $this->setVar( '_Caches', $caches );
794         }
795
796         /**
797          * Search for GNU diff3.
798          */
799         protected function envCheckDiff3() {
800                 $names = array( "gdiff3", "diff3", "diff3.exe" );
801                 $versionInfo = array( '$1 --version 2>&1', 'GNU diffutils' );
802
803                 $diff3 = self::locateExecutableInDefaultPaths( $names, $versionInfo );
804
805                 if ( $diff3 ) {
806                         $this->setVar( 'wgDiff3', $diff3 );
807                 } else {
808                         $this->setVar( 'wgDiff3', false );
809                         $this->showMessage( 'config-diff3-bad' );
810                 }
811         }
812
813         /**
814          * Environment check for ImageMagick and GD.
815          */
816         protected function envCheckGraphics() {
817                 $names = array( wfIsWindows() ? 'convert.exe' : 'convert' );
818                 $convert = self::locateExecutableInDefaultPaths( $names, array( '$1 -version', 'ImageMagick' ) );
819
820                 $this->setVar( 'wgImageMagickConvertCommand', '' );
821                 if ( $convert ) {
822                         $this->setVar( 'wgImageMagickConvertCommand', $convert );
823                         $this->showMessage( 'config-imagemagick', $convert );
824                         return true;
825                 } elseif ( function_exists( 'imagejpeg' ) ) {
826                         $this->showMessage( 'config-gd' );
827                         return true;
828                 } else {
829                         $this->showMessage( 'config-no-scaling' );
830                 }
831         }
832
833         /**
834          * Environment check for setting $IP and $wgScriptPath.
835          */
836         protected function envCheckPath() {
837                 global $IP;
838                 $IP = dirname( dirname( dirname( __FILE__ ) ) );
839
840                 $this->setVar( 'IP', $IP );
841
842                 // PHP_SELF isn't available sometimes, such as when PHP is CGI but
843                 // cgi.fix_pathinfo is disabled. In that case, fall back to SCRIPT_NAME
844                 // to get the path to the current script... hopefully it's reliable. SIGH
845                 if ( !empty( $_SERVER['PHP_SELF'] ) ) {
846                         $path = $_SERVER['PHP_SELF'];
847                 } elseif ( !empty( $_SERVER['SCRIPT_NAME'] ) ) {
848                         $path = $_SERVER['SCRIPT_NAME'];
849                 } elseif ( $this->getVar( 'wgScriptPath' ) ) {
850                         // Some kind soul has set it for us already (e.g. debconf)
851                         return true;
852                 } else {
853                         $this->showError( 'config-no-uri' );
854                         return false;
855                 }
856
857                 $uri = preg_replace( '{^(.*)/(mw-)?config.*$}', '$1', $path );
858                 $this->setVar( 'wgScriptPath', $uri );
859         }
860
861         /**
862          * Environment check for setting the preferred PHP file extension.
863          */
864         protected function envCheckExtension() {
865                 // FIXME: detect this properly
866                 if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) {
867                         $ext = 'php5';
868                 } else {
869                         $ext = 'php';
870                 }
871                 $this->setVar( 'wgScriptExtension', ".$ext" );
872         }
873
874         /**
875          * TODO: document
876          */
877         protected function envCheckShellLocale() {
878                 $os = php_uname( 's' );
879                 $supported = array( 'Linux', 'SunOS', 'HP-UX', 'Darwin' ); # Tested these
880
881                 if ( !in_array( $os, $supported ) ) {
882                         return true;
883                 }
884
885                 # Get a list of available locales.
886                 $ret = false;
887                 $lines = wfShellExec( '/usr/bin/locale -a', $ret );
888
889                 if ( $ret ) {
890                         return true;
891                 }
892
893                 $lines = wfArrayMap( 'trim', explode( "\n", $lines ) );
894                 $candidatesByLocale = array();
895                 $candidatesByLang = array();
896
897                 foreach ( $lines as $line ) {
898                         if ( $line === '' ) {
899                                 continue;
900                         }
901
902                         if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) {
903                                 continue;
904                         }
905
906                         list( $all, $lang, $territory, $charset, $modifier ) = $m;
907
908                         $candidatesByLocale[$m[0]] = $m;
909                         $candidatesByLang[$lang][] = $m;
910                 }
911
912                 # Try the current value of LANG.
913                 if ( isset( $candidatesByLocale[ getenv( 'LANG' ) ] ) ) {
914                         $this->setVar( 'wgShellLocale', getenv( 'LANG' ) );
915                         return true;
916                 }
917
918                 # Try the most common ones.
919                 $commonLocales = array( 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' );
920                 foreach ( $commonLocales as $commonLocale ) {
921                         if ( isset( $candidatesByLocale[$commonLocale] ) ) {
922                                 $this->setVar( 'wgShellLocale', $commonLocale );
923                                 return true;
924                         }
925                 }
926
927                 # Is there an available locale in the Wiki's language?
928                 $wikiLang = $this->getVar( 'wgLanguageCode' );
929
930                 if ( isset( $candidatesByLang[$wikiLang] ) ) {
931                         $m = reset( $candidatesByLang[$wikiLang] );
932                         $this->setVar( 'wgShellLocale', $m[0] );
933                         return true;
934                 }
935
936                 # Are there any at all?
937                 if ( count( $candidatesByLocale ) ) {
938                         $m = reset( $candidatesByLocale );
939                         $this->setVar( 'wgShellLocale', $m[0] );
940                         return true;
941                 }
942
943                 # Give up.
944                 return true;
945         }
946
947         /**
948          * TODO: document
949          */
950         protected function envCheckUploadsDirectory() {
951                 global $IP, $wgServer;
952
953                 $dir = $IP . '/images/';
954                 $url = $wgServer . $this->getVar( 'wgScriptPath' ) . '/images/';
955                 $safe = !$this->dirIsExecutable( $dir, $url );
956
957                 if ( $safe ) {
958                         return true;
959                 } else {
960                         $this->showMessage( 'config-uploads-not-safe', $dir );
961                 }
962         }
963
964         /**
965          * Checks if suhosin.get.max_value_length is set, and if so, sets
966          * $wgResourceLoaderMaxQueryLength to that value in the generated
967          * LocalSettings file
968          */
969         protected function envCheckSuhosinMaxValueLength() {
970                 $maxValueLength = ini_get( 'suhosin.get.max_value_length' );
971                 if ( $maxValueLength > 0 ) {
972                         $this->showMessage( 'config-suhosin-max-value-length', $maxValueLength );
973                 } else {
974                         $maxValueLength = -1;
975                 }
976                 $this->setVar( 'wgResourceLoaderMaxQueryLength', $maxValueLength );
977         }
978
979         /**
980          * Convert a hex string representing a Unicode code point to that code point.
981          * @param $c String
982          * @return string
983          */
984         protected function unicodeChar( $c ) {
985                 $c = hexdec($c);
986                 if ($c <= 0x7F) {
987                         return chr($c);
988                 } else if ($c <= 0x7FF) {
989                         return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F);
990                 } else if ($c <= 0xFFFF) {
991                         return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F)
992                                 . chr(0x80 | $c & 0x3F);
993                 } else if ($c <= 0x10FFFF) {
994                         return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F)
995                                 . chr(0x80 | $c >> 6 & 0x3F)
996                                 . chr(0x80 | $c & 0x3F);
997                 } else {
998                         return false;
999                 }
1000         }
1001
1002
1003         /**
1004          * Check the libicu version
1005          */
1006         protected function envCheckLibicu() {
1007                 $utf8 = function_exists( 'utf8_normalize' );
1008                 $intl = function_exists( 'normalizer_normalize' );
1009
1010                 /**
1011                  * This needs to be updated something that the latest libicu
1012                  * will properly normalize.  This normalization was found at
1013                  * http://www.unicode.org/versions/Unicode5.2.0/#Character_Additions
1014                  * Note that we use the hex representation to create the code
1015                  * points in order to avoid any Unicode-destroying during transit.
1016                  */
1017                 $not_normal_c = $this->unicodeChar("FA6C");
1018                 $normal_c = $this->unicodeChar("242EE");
1019
1020                 $useNormalizer = 'php';
1021                 $needsUpdate = false;
1022
1023                 /**
1024                  * We're going to prefer the pecl extension here unless
1025                  * utf8_normalize is more up to date.
1026                  */
1027                 if( $utf8 ) {
1028                         $useNormalizer = 'utf8';
1029                         $utf8 = utf8_normalize( $not_normal_c, UNORM_NFC );
1030                         if ( $utf8 !== $normal_c ) $needsUpdate = true;
1031                 }
1032                 if( $intl ) {
1033                         $useNormalizer = 'intl';
1034                         $intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C );
1035                         if ( $intl !== $normal_c ) $needsUpdate = true;
1036                 }
1037
1038                 // Uses messages 'config-unicode-using-php', 'config-unicode-using-utf8', 'config-unicode-using-intl'
1039                 if( $useNormalizer === 'php' ) {
1040                         $this->showMessage( 'config-unicode-pure-php-warning' );
1041                 } else {
1042                         $this->showMessage( 'config-unicode-using-' . $useNormalizer );
1043                         if( $needsUpdate ) {
1044                                 $this->showMessage( 'config-unicode-update-warning' );
1045                         }
1046                 }
1047         }
1048
1049         /**
1050          * Get an array of likely places we can find executables. Check a bunch
1051          * of known Unix-like defaults, as well as the PATH environment variable
1052          * (which should maybe make it work for Windows?)
1053          *
1054          * @return Array
1055          */
1056         protected static function getPossibleBinPaths() {
1057                 return array_merge(
1058                         array( '/usr/bin', '/usr/local/bin', '/opt/csw/bin',
1059                                 '/usr/gnu/bin', '/usr/sfw/bin', '/sw/bin', '/opt/local/bin' ),
1060                         explode( PATH_SEPARATOR, getenv( 'PATH' ) )
1061                 );
1062         }
1063
1064         /**
1065          * Search a path for any of the given executable names. Returns the
1066          * executable name if found. Also checks the version string returned
1067          * by each executable.
1068          *
1069          * Used only by environment checks.
1070          *
1071          * @param $path String: path to search
1072          * @param $names Array of executable names
1073          * @param $versionInfo Boolean false or array with two members:
1074          *               0 => Command to run for version check, with $1 for the full executable name
1075          *               1 => String to compare the output with
1076          *
1077          * If $versionInfo is not false, only executables with a version
1078          * matching $versionInfo[1] will be returned.
1079          */
1080         public static function locateExecutable( $path, $names, $versionInfo = false ) {
1081                 if ( !is_array( $names ) ) {
1082                         $names = array( $names );
1083                 }
1084
1085                 foreach ( $names as $name ) {
1086                         $command = $path . DIRECTORY_SEPARATOR . $name;
1087
1088                         wfSuppressWarnings();
1089                         $file_exists = file_exists( $command );
1090                         wfRestoreWarnings();
1091
1092                         if ( $file_exists ) {
1093                                 if ( !$versionInfo ) {
1094                                         return $command;
1095                                 }
1096
1097                                 $file = str_replace( '$1', wfEscapeShellArg( $command ), $versionInfo[0] );
1098                                 if ( strstr( wfShellExec( $file ), $versionInfo[1] ) !== false ) {
1099                                         return $command;
1100                                 }
1101                         }
1102                 }
1103                 return false;
1104         }
1105
1106         /**
1107          * Same as locateExecutable(), but checks in getPossibleBinPaths() by default
1108          * @see locateExecutable()
1109          */
1110         public static function locateExecutableInDefaultPaths( $names, $versionInfo = false ) {
1111                 foreach( self::getPossibleBinPaths() as $path ) {
1112                         $exe = self::locateExecutable( $path, $names, $versionInfo );
1113                         if( $exe !== false ) {
1114                                 return $exe;
1115                         }
1116                 }
1117                 return false;
1118         }
1119
1120         /**
1121          * Checks if scripts located in the given directory can be executed via the given URL.
1122          *
1123          * Used only by environment checks.
1124          */
1125         public function dirIsExecutable( $dir, $url ) {
1126                 $scriptTypes = array(
1127                         'php' => array(
1128                                 "<?php echo 'ex' . 'ec';",
1129                                 "#!/var/env php5\n<?php echo 'ex' . 'ec';",
1130                         ),
1131                 );
1132
1133                 // it would be good to check other popular languages here, but it'll be slow.
1134
1135                 wfSuppressWarnings();
1136
1137                 foreach ( $scriptTypes as $ext => $contents ) {
1138                         foreach ( $contents as $source ) {
1139                                 $file = 'exectest.' . $ext;
1140
1141                                 if ( !file_put_contents( $dir . $file, $source ) ) {
1142                                         break;
1143                                 }
1144
1145                                 try {
1146                                         $text = Http::get( $url . $file, array( 'timeout' => 3 ) );
1147                                 }
1148                                 catch( MWException $e ) {
1149                                         // Http::get throws with allow_url_fopen = false and no curl extension.
1150                                         $text = null;
1151                                 }
1152                                 unlink( $dir . $file );
1153
1154                                 if ( $text == 'exec' ) {
1155                                         wfRestoreWarnings();
1156                                         return $ext;
1157                                 }
1158                         }
1159                 }
1160
1161                 wfRestoreWarnings();
1162
1163                 return false;
1164         }
1165
1166         /**
1167          * ParserOptions are constructed before we determined the language, so fix it
1168          */
1169         public function setParserLanguage( $lang ) {
1170                 $this->parserOptions->setTargetLanguage( $lang );
1171                 $this->parserOptions->setUserLang( $lang->getCode() );
1172         }
1173
1174         /**
1175          * Overridden by WebInstaller to provide lastPage parameters.
1176          */
1177         protected function getDocUrl( $page ) {
1178                 return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page );
1179         }
1180
1181         /**
1182          * Finds extensions that follow the format /extensions/Name/Name.php,
1183          * and returns an array containing the value for 'Name' for each found extension.
1184          *
1185          * @return array
1186          */
1187         public function findExtensions() {
1188                 if( $this->getVar( 'IP' ) === null ) {
1189                         return false;
1190                 }
1191
1192                 $exts = array();
1193                 $dir = $this->getVar( 'IP' ) . '/extensions';
1194                 $dh = opendir( $dir );
1195
1196                 while ( ( $file = readdir( $dh ) ) !== false ) {
1197                         if( file_exists( "$dir/$file/$file.php" ) ) {
1198                                 $exts[] = $file;
1199                         }
1200                 }
1201
1202                 return $exts;
1203         }
1204
1205         /**
1206          * Installs the auto-detected extensions.
1207          *
1208          * @return Status
1209          */
1210         protected function includeExtensions() {
1211                 global $IP;
1212                 $exts = $this->getVar( '_Extensions' );
1213                 $IP = $this->getVar( 'IP' );
1214
1215                 /**
1216                  * We need to include DefaultSettings before including extensions to avoid
1217                  * warnings about unset variables. However, the only thing we really
1218                  * want here is $wgHooks['LoadExtensionSchemaUpdates']. This won't work
1219                  * if the extension has hidden hook registration in $wgExtensionFunctions,
1220                  * but we're not opening that can of worms
1221                  * @see https://bugzilla.wikimedia.org/show_bug.cgi?id=26857
1222                  */
1223                 global $wgAutoloadClasses;
1224                 require( "$IP/includes/DefaultSettings.php" );
1225
1226                 foreach( $exts as $e ) {
1227                         require_once( $IP . '/extensions' . "/$e/$e.php" );
1228                 }
1229
1230                 $hooksWeWant = isset( $wgHooks['LoadExtensionSchemaUpdates'] ) ?
1231                         $wgHooks['LoadExtensionSchemaUpdates'] : array();
1232
1233                 // Unset everyone else's hooks. Lord knows what someone might be doing
1234                 // in ParserFirstCallInit (see bug 27171)
1235                 $GLOBALS['wgHooks'] = array( 'LoadExtensionSchemaUpdates' => $hooksWeWant );
1236
1237                 return Status::newGood();
1238         }
1239
1240         /**
1241          * Get an array of install steps. Should always be in the format of
1242          * array(
1243          *   'name'     => 'someuniquename',
1244          *   'callback' => array( $obj, 'method' ),
1245          * )
1246          * There must be a config-install-$name message defined per step, which will
1247          * be shown on install.
1248          *
1249          * @param $installer DatabaseInstaller so we can make callbacks
1250          * @return array
1251          */
1252         protected function getInstallSteps( DatabaseInstaller $installer ) {
1253                 $coreInstallSteps = array(
1254                         array( 'name' => 'database',   'callback' => array( $installer, 'setupDatabase' ) ),
1255                         array( 'name' => 'tables',     'callback' => array( $installer, 'createTables' ) ),
1256                         array( 'name' => 'interwiki',  'callback' => array( $installer, 'populateInterwikiTable' ) ),
1257                         array( 'name' => 'stats',      'callback' => array( $this, 'populateSiteStats' ) ),
1258                         array( 'name' => 'keys',       'callback' => array( $this, 'generateKeys' ) ),
1259                         array( 'name' => 'sysop',      'callback' => array( $this, 'createSysop' ) ),
1260                         array( 'name' => 'mainpage',   'callback' => array( $this, 'createMainpage' ) ),
1261                 );
1262
1263                 // Build the array of install steps starting from the core install list,
1264                 // then adding any callbacks that wanted to attach after a given step
1265                 foreach( $coreInstallSteps as $step ) {
1266                         $this->installSteps[] = $step;
1267                         if( isset( $this->extraInstallSteps[ $step['name'] ] ) ) {
1268                                 $this->installSteps = array_merge(
1269                                         $this->installSteps,
1270                                         $this->extraInstallSteps[ $step['name'] ]
1271                                 );
1272                         }
1273                 }
1274
1275                 // Prepend any steps that want to be at the beginning
1276                 if( isset( $this->extraInstallSteps['BEGINNING'] ) ) {
1277                         $this->installSteps = array_merge(
1278                                 $this->extraInstallSteps['BEGINNING'],
1279                                 $this->installSteps
1280                         );
1281                 }
1282
1283                 // Extensions should always go first, chance to tie into hooks and such
1284                 if( count( $this->getVar( '_Extensions' ) ) ) {
1285                         array_unshift( $this->installSteps,
1286                                 array( 'name' => 'extensions', 'callback' => array( $this, 'includeExtensions' ) )
1287                         );
1288                         $this->installSteps[] = array(
1289                                 'name' => 'extension-tables',
1290                                 'callback' => array( $installer, 'createExtensionTables' )
1291                         );
1292                 }
1293                 return $this->installSteps;
1294         }
1295
1296         /**
1297          * Actually perform the installation.
1298          *
1299          * @param $startCB Array A callback array for the beginning of each step
1300          * @param $endCB Array A callback array for the end of each step
1301          *
1302          * @return Array of Status objects
1303          */
1304         public function performInstallation( $startCB, $endCB ) {
1305                 $installResults = array();
1306                 $installer = $this->getDBInstaller();
1307                 $installer->preInstall();
1308                 $steps = $this->getInstallSteps( $installer );
1309                 foreach( $steps as $stepObj ) {
1310                         $name = $stepObj['name'];
1311                         call_user_func_array( $startCB, array( $name ) );
1312
1313                         // Perform the callback step
1314                         $status = call_user_func( $stepObj['callback'], $installer );
1315
1316                         // Output and save the results
1317                         call_user_func( $endCB, $name, $status );
1318                         $installResults[$name] = $status;
1319
1320                         // If we've hit some sort of fatal, we need to bail.
1321                         // Callback already had a chance to do output above.
1322                         if( !$status->isOk() ) {
1323                                 break;
1324                         }
1325                 }
1326                 if( $status->isOk() ) {
1327                         $this->setVar( '_InstallDone', true );
1328                 }
1329                 return $installResults;
1330         }
1331
1332         /**
1333          * Generate $wgSecretKey. Will warn if we had to use mt_rand() instead of
1334          * /dev/urandom
1335          *
1336          * @return Status
1337          */
1338         public function generateKeys() {
1339                 $keys = array( 'wgSecretKey' => 64 );
1340                 if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) {
1341                         $keys['wgUpgradeKey'] = 16;
1342                 }
1343                 return $this->doGenerateKeys( $keys );
1344         }
1345
1346         /**
1347          * Generate a secret value for variables using either
1348          * /dev/urandom or mt_rand(). Produce a warning in the later case.
1349          *
1350          * @param $keys Array
1351          * @return Status
1352          */
1353         protected function doGenerateKeys( $keys ) {
1354                 $status = Status::newGood();
1355
1356                 wfSuppressWarnings();
1357                 $file = fopen( "/dev/urandom", "r" );
1358                 wfRestoreWarnings();
1359
1360                 foreach ( $keys as $name => $length ) {
1361                         if ( $file ) {
1362                                         $secretKey = bin2hex( fread( $file, $length / 2 ) );
1363                         } else {
1364                                 $secretKey = '';
1365
1366                                 for ( $i = 0; $i < $length / 8; $i++ ) {
1367                                         $secretKey .= dechex( mt_rand( 0, 0x7fffffff ) );
1368                                 }
1369                         }
1370
1371                         $this->setVar( $name, $secretKey );
1372                 }
1373
1374                 if ( $file ) {
1375                         fclose( $file );
1376                 } else {
1377                         $names = array_keys ( $keys );
1378                         $names = preg_replace( '/^(.*)$/', '\$$1', $names );
1379                         global $wgLang;
1380                         $status->warning( 'config-insecure-keys', $wgLang->listToText( $names ), count( $names ) );
1381                 }
1382
1383                 return $status;
1384         }
1385
1386         /**
1387          * Create the first user account, grant it sysop and bureaucrat rights
1388          *
1389          * @return Status
1390          */
1391         protected function createSysop() {
1392                 $name = $this->getVar( '_AdminName' );
1393                 $user = User::newFromName( $name );
1394
1395                 if ( !$user ) {
1396                         // We should've validated this earlier anyway!
1397                         return Status::newFatal( 'config-admin-error-user', $name );
1398                 }
1399
1400                 if ( $user->idForName() == 0 ) {
1401                         $user->addToDatabase();
1402
1403                         try {
1404                                 $user->setPassword( $this->getVar( '_AdminPassword' ) );
1405                         } catch( PasswordError $pwe ) {
1406                                 return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() );
1407                         }
1408
1409                         $user->addGroup( 'sysop' );
1410                         $user->addGroup( 'bureaucrat' );
1411                         if( $this->getVar( '_AdminEmail' ) ) {
1412                                 $user->setEmail( $this->getVar( '_AdminEmail' ) );
1413                         }
1414                         $user->saveSettings();
1415
1416                         // Update user count
1417                         $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 );
1418                         $ssUpdate->doUpdate();
1419                 }
1420                 $status = Status::newGood();
1421
1422                 if( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) {
1423                         $this->subscribeToMediaWikiAnnounce( $status );
1424                 }
1425
1426                 return $status;
1427         }
1428
1429         private function subscribeToMediaWikiAnnounce( Status $s ) {
1430                 $params = array(
1431                         'email'    => $this->getVar( '_AdminEmail' ),
1432                         'language' => 'en',
1433                         'digest'   => 0
1434                 );
1435
1436                 // Mailman doesn't support as many languages as we do, so check to make
1437                 // sure their selected language is available
1438                 $myLang = $this->getVar( '_UserLang' );
1439                 if( in_array( $myLang, $this->mediaWikiAnnounceLanguages ) ) {
1440                         $myLang = $myLang == 'pt-br' ? 'pt_BR' : $myLang; // rewrite to Mailman's pt_BR
1441                         $params['language'] = $myLang;
1442                 }
1443
1444                 $res = MWHttpRequest::factory( $this->mediaWikiAnnounceUrl,
1445                         array( 'method' => 'POST', 'postData' => $params ) )->execute();
1446                 if( !$res->isOK() ) {
1447                         $s->warning( 'config-install-subscribe-fail', $res->getMessage() );
1448                 }
1449         }
1450
1451         /**
1452          * Insert Main Page with default content.
1453          *
1454          * @return Status
1455          */
1456         protected function createMainpage( DatabaseInstaller $installer ) {
1457                 $status = Status::newGood();
1458                 try {
1459                         $article = new Article( Title::newMainPage() );
1460                         $article->doEdit( wfMsgForContent( 'mainpagetext' ) . "\n\n" .
1461                                                                 wfMsgForContent( 'mainpagedocfooter' ),
1462                                                                 '',
1463                                                                 EDIT_NEW,
1464                                                                 false,
1465                                                                 User::newFromName( 'MediaWiki default' ) );
1466                 } catch (MWException $e) {
1467                         //using raw, because $wgShowExceptionDetails can not be set yet
1468                         $status->fatal( 'config-install-mainpage-failed', $e->getMessage() );
1469                 }
1470
1471                 return $status;
1472         }
1473
1474         /**
1475          * Override the necessary bits of the config to run an installation.
1476          */
1477         public static function overrideConfig() {
1478                 define( 'MW_NO_SESSION', 1 );
1479
1480                 // Don't access the database
1481                 $GLOBALS['wgUseDatabaseMessages'] = false;
1482                 // Debug-friendly
1483                 $GLOBALS['wgShowExceptionDetails'] = true;
1484                 // Don't break forms
1485                 $GLOBALS['wgExternalLinkTarget'] = '_blank';
1486
1487                 // Extended debugging
1488                 $GLOBALS['wgShowSQLErrors'] = true;
1489                 $GLOBALS['wgShowDBErrorBacktrace'] = true;
1490
1491                 // Allow multiple ob_flush() calls
1492                 $GLOBALS['wgDisableOutputCompression'] = true;
1493
1494                 // Use a sensible cookie prefix (not my_wiki)
1495                 $GLOBALS['wgCookiePrefix'] = 'mw_installer';
1496
1497                 // Some of the environment checks make shell requests, remove limits
1498                 $GLOBALS['wgMaxShellMemory'] = 0;
1499         }
1500
1501         /**
1502          * Add an installation step following the given step.
1503          *
1504          * @param $callback Array A valid installation callback array, in this form:
1505          *    array( 'name' => 'some-unique-name', 'callback' => array( $obj, 'function' ) );
1506          * @param $findStep String the step to find. Omit to put the step at the beginning
1507          */
1508         public function addInstallStep( $callback, $findStep = 'BEGINNING' ) {
1509                 $this->extraInstallSteps[$findStep][] = $callback;
1510         }
1511 }