]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/installer/Installer.php
MediaWiki 1.17.2-scripts
[autoinstallsdev/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( 'register_globals' ) ) {
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                                 if ( $name == 'xcache' && !wfIniGetBool( 'xcache.var_size' ) ) {
786                                         continue;
787                                 }
788                                 $caches[$name] = true;
789                         }
790                 }
791
792                 if ( !$caches ) {
793                         $this->showMessage( 'config-no-cache' );
794                 }
795
796                 $this->setVar( '_Caches', $caches );
797         }
798
799         /**
800          * Search for GNU diff3.
801          */
802         protected function envCheckDiff3() {
803                 $names = array( "gdiff3", "diff3", "diff3.exe" );
804                 $versionInfo = array( '$1 --version 2>&1', 'GNU diffutils' );
805
806                 $diff3 = self::locateExecutableInDefaultPaths( $names, $versionInfo );
807
808                 if ( $diff3 ) {
809                         $this->setVar( 'wgDiff3', $diff3 );
810                 } else {
811                         $this->setVar( 'wgDiff3', false );
812                         $this->showMessage( 'config-diff3-bad' );
813                 }
814         }
815
816         /**
817          * Environment check for ImageMagick and GD.
818          */
819         protected function envCheckGraphics() {
820                 $names = array( wfIsWindows() ? 'convert.exe' : 'convert' );
821                 $convert = self::locateExecutableInDefaultPaths( $names, array( '$1 -version', 'ImageMagick' ) );
822
823                 $this->setVar( 'wgImageMagickConvertCommand', '' );
824                 if ( $convert ) {
825                         $this->setVar( 'wgImageMagickConvertCommand', $convert );
826                         $this->showMessage( 'config-imagemagick', $convert );
827                         return true;
828                 } elseif ( function_exists( 'imagejpeg' ) ) {
829                         $this->showMessage( 'config-gd' );
830                         return true;
831                 } else {
832                         $this->showMessage( 'config-no-scaling' );
833                 }
834         }
835
836         /**
837          * Environment check for setting $IP and $wgScriptPath.
838          */
839         protected function envCheckPath() {
840                 global $IP;
841                 $IP = dirname( dirname( dirname( __FILE__ ) ) );
842                 $this->setVar( 'IP', $IP );
843         }
844
845         /**
846          * Environment check for setting the preferred PHP file extension.
847          */
848         protected function envCheckExtension() {
849                 // FIXME: detect this properly
850                 if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) {
851                         $ext = 'php5';
852                 } else {
853                         $ext = 'php';
854                 }
855                 $this->setVar( 'wgScriptExtension', ".$ext" );
856         }
857
858         /**
859          * TODO: document
860          */
861         protected function envCheckShellLocale() {
862                 $os = php_uname( 's' );
863                 $supported = array( 'Linux', 'SunOS', 'HP-UX', 'Darwin' ); # Tested these
864
865                 if ( !in_array( $os, $supported ) ) {
866                         return true;
867                 }
868
869                 # Get a list of available locales.
870                 $ret = false;
871                 $lines = wfShellExec( '/usr/bin/locale -a', $ret );
872
873                 if ( $ret ) {
874                         return true;
875                 }
876
877                 $lines = wfArrayMap( 'trim', explode( "\n", $lines ) );
878                 $candidatesByLocale = array();
879                 $candidatesByLang = array();
880
881                 foreach ( $lines as $line ) {
882                         if ( $line === '' ) {
883                                 continue;
884                         }
885
886                         if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) {
887                                 continue;
888                         }
889
890                         list( $all, $lang, $territory, $charset, $modifier ) = $m;
891
892                         $candidatesByLocale[$m[0]] = $m;
893                         $candidatesByLang[$lang][] = $m;
894                 }
895
896                 # Try the current value of LANG.
897                 if ( isset( $candidatesByLocale[ getenv( 'LANG' ) ] ) ) {
898                         $this->setVar( 'wgShellLocale', getenv( 'LANG' ) );
899                         return true;
900                 }
901
902                 # Try the most common ones.
903                 $commonLocales = array( 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' );
904                 foreach ( $commonLocales as $commonLocale ) {
905                         if ( isset( $candidatesByLocale[$commonLocale] ) ) {
906                                 $this->setVar( 'wgShellLocale', $commonLocale );
907                                 return true;
908                         }
909                 }
910
911                 # Is there an available locale in the Wiki's language?
912                 $wikiLang = $this->getVar( 'wgLanguageCode' );
913
914                 if ( isset( $candidatesByLang[$wikiLang] ) ) {
915                         $m = reset( $candidatesByLang[$wikiLang] );
916                         $this->setVar( 'wgShellLocale', $m[0] );
917                         return true;
918                 }
919
920                 # Are there any at all?
921                 if ( count( $candidatesByLocale ) ) {
922                         $m = reset( $candidatesByLocale );
923                         $this->setVar( 'wgShellLocale', $m[0] );
924                         return true;
925                 }
926
927                 # Give up.
928                 return true;
929         }
930
931         /**
932          * TODO: document
933          */
934         protected function envCheckUploadsDirectory() {
935                 global $IP, $wgServer;
936
937                 $dir = $IP . '/images/';
938                 $url = $wgServer . $this->getVar( 'wgScriptPath' ) . '/images/';
939                 $safe = !$this->dirIsExecutable( $dir, $url );
940
941                 if ( $safe ) {
942                         return true;
943                 } else {
944                         $this->showMessage( 'config-uploads-not-safe', $dir );
945                 }
946         }
947
948         /**
949          * Checks if suhosin.get.max_value_length is set, and if so, sets
950          * $wgResourceLoaderMaxQueryLength to that value in the generated
951          * LocalSettings file
952          */
953         protected function envCheckSuhosinMaxValueLength() {
954                 $maxValueLength = ini_get( 'suhosin.get.max_value_length' );
955                 if ( $maxValueLength > 0 ) {
956                         $this->showMessage( 'config-suhosin-max-value-length', $maxValueLength );
957                 } else {
958                         $maxValueLength = -1;
959                 }
960                 $this->setVar( 'wgResourceLoaderMaxQueryLength', $maxValueLength );
961         }
962
963         /**
964          * Convert a hex string representing a Unicode code point to that code point.
965          * @param $c String
966          * @return string
967          */
968         protected function unicodeChar( $c ) {
969                 $c = hexdec($c);
970                 if ($c <= 0x7F) {
971                         return chr($c);
972                 } else if ($c <= 0x7FF) {
973                         return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F);
974                 } else if ($c <= 0xFFFF) {
975                         return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F)
976                                 . chr(0x80 | $c & 0x3F);
977                 } else if ($c <= 0x10FFFF) {
978                         return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F)
979                                 . chr(0x80 | $c >> 6 & 0x3F)
980                                 . chr(0x80 | $c & 0x3F);
981                 } else {
982                         return false;
983                 }
984         }
985
986
987         /**
988          * Check the libicu version
989          */
990         protected function envCheckLibicu() {
991                 $utf8 = function_exists( 'utf8_normalize' );
992                 $intl = function_exists( 'normalizer_normalize' );
993
994                 /**
995                  * This needs to be updated something that the latest libicu
996                  * will properly normalize.  This normalization was found at
997                  * http://www.unicode.org/versions/Unicode5.2.0/#Character_Additions
998                  * Note that we use the hex representation to create the code
999                  * points in order to avoid any Unicode-destroying during transit.
1000                  */
1001                 $not_normal_c = $this->unicodeChar("FA6C");
1002                 $normal_c = $this->unicodeChar("242EE");
1003
1004                 $useNormalizer = 'php';
1005                 $needsUpdate = false;
1006
1007                 /**
1008                  * We're going to prefer the pecl extension here unless
1009                  * utf8_normalize is more up to date.
1010                  */
1011                 if( $utf8 ) {
1012                         $useNormalizer = 'utf8';
1013                         $utf8 = utf8_normalize( $not_normal_c, UNORM_NFC );
1014                         if ( $utf8 !== $normal_c ) $needsUpdate = true;
1015                 }
1016                 if( $intl ) {
1017                         $useNormalizer = 'intl';
1018                         $intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C );
1019                         if ( $intl !== $normal_c ) $needsUpdate = true;
1020                 }
1021
1022                 // Uses messages 'config-unicode-using-php', 'config-unicode-using-utf8', 'config-unicode-using-intl'
1023                 if( $useNormalizer === 'php' ) {
1024                         $this->showMessage( 'config-unicode-pure-php-warning' );
1025                 } else {
1026                         $this->showMessage( 'config-unicode-using-' . $useNormalizer );
1027                         if( $needsUpdate ) {
1028                                 $this->showMessage( 'config-unicode-update-warning' );
1029                         }
1030                 }
1031         }
1032
1033         /**
1034          * Get an array of likely places we can find executables. Check a bunch
1035          * of known Unix-like defaults, as well as the PATH environment variable
1036          * (which should maybe make it work for Windows?)
1037          *
1038          * @return Array
1039          */
1040         protected static function getPossibleBinPaths() {
1041                 return array_merge(
1042                         array( '/usr/bin', '/usr/local/bin', '/opt/csw/bin',
1043                                 '/usr/gnu/bin', '/usr/sfw/bin', '/sw/bin', '/opt/local/bin' ),
1044                         explode( PATH_SEPARATOR, getenv( 'PATH' ) )
1045                 );
1046         }
1047
1048         /**
1049          * Search a path for any of the given executable names. Returns the
1050          * executable name if found. Also checks the version string returned
1051          * by each executable.
1052          *
1053          * Used only by environment checks.
1054          *
1055          * @param $path String: path to search
1056          * @param $names Array of executable names
1057          * @param $versionInfo Boolean false or array with two members:
1058          *               0 => Command to run for version check, with $1 for the full executable name
1059          *               1 => String to compare the output with
1060          *
1061          * If $versionInfo is not false, only executables with a version
1062          * matching $versionInfo[1] will be returned.
1063          */
1064         public static function locateExecutable( $path, $names, $versionInfo = false ) {
1065                 if ( !is_array( $names ) ) {
1066                         $names = array( $names );
1067                 }
1068
1069                 foreach ( $names as $name ) {
1070                         $command = $path . DIRECTORY_SEPARATOR . $name;
1071
1072                         wfSuppressWarnings();
1073                         $file_exists = file_exists( $command );
1074                         wfRestoreWarnings();
1075
1076                         if ( $file_exists ) {
1077                                 if ( !$versionInfo ) {
1078                                         return $command;
1079                                 }
1080
1081                                 $file = str_replace( '$1', wfEscapeShellArg( $command ), $versionInfo[0] );
1082                                 if ( strstr( wfShellExec( $file ), $versionInfo[1] ) !== false ) {
1083                                         return $command;
1084                                 }
1085                         }
1086                 }
1087                 return false;
1088         }
1089
1090         /**
1091          * Same as locateExecutable(), but checks in getPossibleBinPaths() by default
1092          * @see locateExecutable()
1093          */
1094         public static function locateExecutableInDefaultPaths( $names, $versionInfo = false ) {
1095                 foreach( self::getPossibleBinPaths() as $path ) {
1096                         $exe = self::locateExecutable( $path, $names, $versionInfo );
1097                         if( $exe !== false ) {
1098                                 return $exe;
1099                         }
1100                 }
1101                 return false;
1102         }
1103
1104         /**
1105          * Checks if scripts located in the given directory can be executed via the given URL.
1106          *
1107          * Used only by environment checks.
1108          */
1109         public function dirIsExecutable( $dir, $url ) {
1110                 $scriptTypes = array(
1111                         'php' => array(
1112                                 "<?php echo 'ex' . 'ec';",
1113                                 "#!/var/env php5\n<?php echo 'ex' . 'ec';",
1114                         ),
1115                 );
1116
1117                 // it would be good to check other popular languages here, but it'll be slow.
1118
1119                 wfSuppressWarnings();
1120
1121                 foreach ( $scriptTypes as $ext => $contents ) {
1122                         foreach ( $contents as $source ) {
1123                                 $file = 'exectest.' . $ext;
1124
1125                                 if ( !file_put_contents( $dir . $file, $source ) ) {
1126                                         break;
1127                                 }
1128
1129                                 try {
1130                                         $text = Http::get( $url . $file, array( 'timeout' => 3 ) );
1131                                 }
1132                                 catch( MWException $e ) {
1133                                         // Http::get throws with allow_url_fopen = false and no curl extension.
1134                                         $text = null;
1135                                 }
1136                                 unlink( $dir . $file );
1137
1138                                 if ( $text == 'exec' ) {
1139                                         wfRestoreWarnings();
1140                                         return $ext;
1141                                 }
1142                         }
1143                 }
1144
1145                 wfRestoreWarnings();
1146
1147                 return false;
1148         }
1149
1150         /**
1151          * ParserOptions are constructed before we determined the language, so fix it
1152          */
1153         public function setParserLanguage( $lang ) {
1154                 $this->parserOptions->setTargetLanguage( $lang );
1155                 $this->parserOptions->setUserLang( $lang->getCode() );
1156         }
1157
1158         /**
1159          * Overridden by WebInstaller to provide lastPage parameters.
1160          */
1161         protected function getDocUrl( $page ) {
1162                 return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page );
1163         }
1164
1165         /**
1166          * Finds extensions that follow the format /extensions/Name/Name.php,
1167          * and returns an array containing the value for 'Name' for each found extension.
1168          *
1169          * @return array
1170          */
1171         public function findExtensions() {
1172                 if( $this->getVar( 'IP' ) === null ) {
1173                         return false;
1174                 }
1175
1176                 $exts = array();
1177                 $dir = $this->getVar( 'IP' ) . '/extensions';
1178                 $dh = opendir( $dir );
1179
1180                 while ( ( $file = readdir( $dh ) ) !== false ) {
1181                         if( file_exists( "$dir/$file/$file.php" ) ) {
1182                                 $exts[] = $file;
1183                         }
1184                 }
1185
1186                 return $exts;
1187         }
1188
1189         /**
1190          * Installs the auto-detected extensions.
1191          *
1192          * @return Status
1193          */
1194         protected function includeExtensions() {
1195                 global $IP;
1196                 $exts = $this->getVar( '_Extensions' );
1197                 $IP = $this->getVar( 'IP' );
1198
1199                 /**
1200                  * We need to include DefaultSettings before including extensions to avoid
1201                  * warnings about unset variables. However, the only thing we really
1202                  * want here is $wgHooks['LoadExtensionSchemaUpdates']. This won't work
1203                  * if the extension has hidden hook registration in $wgExtensionFunctions,
1204                  * but we're not opening that can of worms
1205                  * @see https://bugzilla.wikimedia.org/show_bug.cgi?id=26857
1206                  */
1207                 global $wgAutoloadClasses;
1208                 require( "$IP/includes/DefaultSettings.php" );
1209
1210                 foreach( $exts as $e ) {
1211                         require_once( $IP . '/extensions' . "/$e/$e.php" );
1212                 }
1213
1214                 $hooksWeWant = isset( $wgHooks['LoadExtensionSchemaUpdates'] ) ?
1215                         $wgHooks['LoadExtensionSchemaUpdates'] : array();
1216
1217                 // Unset everyone else's hooks. Lord knows what someone might be doing
1218                 // in ParserFirstCallInit (see bug 27171)
1219                 $GLOBALS['wgHooks'] = array( 'LoadExtensionSchemaUpdates' => $hooksWeWant );
1220
1221                 return Status::newGood();
1222         }
1223
1224         /**
1225          * Get an array of install steps. Should always be in the format of
1226          * array(
1227          *   'name'     => 'someuniquename',
1228          *   'callback' => array( $obj, 'method' ),
1229          * )
1230          * There must be a config-install-$name message defined per step, which will
1231          * be shown on install.
1232          *
1233          * @param $installer DatabaseInstaller so we can make callbacks
1234          * @return array
1235          */
1236         protected function getInstallSteps( DatabaseInstaller $installer ) {
1237                 $coreInstallSteps = array(
1238                         array( 'name' => 'database',   'callback' => array( $installer, 'setupDatabase' ) ),
1239                         array( 'name' => 'tables',     'callback' => array( $installer, 'createTables' ) ),
1240                         array( 'name' => 'interwiki',  'callback' => array( $installer, 'populateInterwikiTable' ) ),
1241                         array( 'name' => 'stats',      'callback' => array( $this, 'populateSiteStats' ) ),
1242                         array( 'name' => 'keys',       'callback' => array( $this, 'generateKeys' ) ),
1243                         array( 'name' => 'sysop',      'callback' => array( $this, 'createSysop' ) ),
1244                         array( 'name' => 'mainpage',   'callback' => array( $this, 'createMainpage' ) ),
1245                 );
1246
1247                 // Build the array of install steps starting from the core install list,
1248                 // then adding any callbacks that wanted to attach after a given step
1249                 foreach( $coreInstallSteps as $step ) {
1250                         $this->installSteps[] = $step;
1251                         if( isset( $this->extraInstallSteps[ $step['name'] ] ) ) {
1252                                 $this->installSteps = array_merge(
1253                                         $this->installSteps,
1254                                         $this->extraInstallSteps[ $step['name'] ]
1255                                 );
1256                         }
1257                 }
1258
1259                 // Prepend any steps that want to be at the beginning
1260                 if( isset( $this->extraInstallSteps['BEGINNING'] ) ) {
1261                         $this->installSteps = array_merge(
1262                                 $this->extraInstallSteps['BEGINNING'],
1263                                 $this->installSteps
1264                         );
1265                 }
1266
1267                 // Extensions should always go first, chance to tie into hooks and such
1268                 if( count( $this->getVar( '_Extensions' ) ) ) {
1269                         array_unshift( $this->installSteps,
1270                                 array( 'name' => 'extensions', 'callback' => array( $this, 'includeExtensions' ) )
1271                         );
1272                         $this->installSteps[] = array(
1273                                 'name' => 'extension-tables',
1274                                 'callback' => array( $installer, 'createExtensionTables' )
1275                         );
1276                 }
1277                 return $this->installSteps;
1278         }
1279
1280         /**
1281          * Actually perform the installation.
1282          *
1283          * @param $startCB Array A callback array for the beginning of each step
1284          * @param $endCB Array A callback array for the end of each step
1285          *
1286          * @return Array of Status objects
1287          */
1288         public function performInstallation( $startCB, $endCB ) {
1289                 $installResults = array();
1290                 $installer = $this->getDBInstaller();
1291                 $installer->preInstall();
1292                 $steps = $this->getInstallSteps( $installer );
1293                 foreach( $steps as $stepObj ) {
1294                         $name = $stepObj['name'];
1295                         call_user_func_array( $startCB, array( $name ) );
1296
1297                         // Perform the callback step
1298                         $status = call_user_func( $stepObj['callback'], $installer );
1299
1300                         // Output and save the results
1301                         call_user_func( $endCB, $name, $status );
1302                         $installResults[$name] = $status;
1303
1304                         // If we've hit some sort of fatal, we need to bail.
1305                         // Callback already had a chance to do output above.
1306                         if( !$status->isOk() ) {
1307                                 break;
1308                         }
1309                 }
1310                 if( $status->isOk() ) {
1311                         $this->setVar( '_InstallDone', true );
1312                 }
1313                 return $installResults;
1314         }
1315
1316         /**
1317          * Generate $wgSecretKey. Will warn if we had to use mt_rand() instead of
1318          * /dev/urandom
1319          *
1320          * @return Status
1321          */
1322         public function generateKeys() {
1323                 $keys = array( 'wgSecretKey' => 64 );
1324                 if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) {
1325                         $keys['wgUpgradeKey'] = 16;
1326                 }
1327                 return $this->doGenerateKeys( $keys );
1328         }
1329
1330         /**
1331          * Generate a secret value for variables using either
1332          * /dev/urandom or mt_rand(). Produce a warning in the later case.
1333          *
1334          * @param $keys Array
1335          * @return Status
1336          */
1337         protected function doGenerateKeys( $keys ) {
1338                 $status = Status::newGood();
1339
1340                 wfSuppressWarnings();
1341                 $file = fopen( "/dev/urandom", "r" );
1342                 wfRestoreWarnings();
1343
1344                 foreach ( $keys as $name => $length ) {
1345                         if ( $file ) {
1346                                         $secretKey = bin2hex( fread( $file, $length / 2 ) );
1347                         } else {
1348                                 $secretKey = '';
1349
1350                                 for ( $i = 0; $i < $length / 8; $i++ ) {
1351                                         $secretKey .= dechex( mt_rand( 0, 0x7fffffff ) );
1352                                 }
1353                         }
1354
1355                         $this->setVar( $name, $secretKey );
1356                 }
1357
1358                 if ( $file ) {
1359                         fclose( $file );
1360                 } else {
1361                         $names = array_keys ( $keys );
1362                         $names = preg_replace( '/^(.*)$/', '\$$1', $names );
1363                         global $wgLang;
1364                         $status->warning( 'config-insecure-keys', $wgLang->listToText( $names ), count( $names ) );
1365                 }
1366
1367                 return $status;
1368         }
1369
1370         /**
1371          * Create the first user account, grant it sysop and bureaucrat rights
1372          *
1373          * @return Status
1374          */
1375         protected function createSysop() {
1376                 $name = $this->getVar( '_AdminName' );
1377                 $user = User::newFromName( $name );
1378
1379                 if ( !$user ) {
1380                         // We should've validated this earlier anyway!
1381                         return Status::newFatal( 'config-admin-error-user', $name );
1382                 }
1383
1384                 if ( $user->idForName() == 0 ) {
1385                         $user->addToDatabase();
1386
1387                         try {
1388                                 $user->setPassword( $this->getVar( '_AdminPassword' ) );
1389                         } catch( PasswordError $pwe ) {
1390                                 return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() );
1391                         }
1392
1393                         $user->addGroup( 'sysop' );
1394                         $user->addGroup( 'bureaucrat' );
1395                         if( $this->getVar( '_AdminEmail' ) ) {
1396                                 $user->setEmail( $this->getVar( '_AdminEmail' ) );
1397                         }
1398                         $user->saveSettings();
1399
1400                         // Update user count
1401                         $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 );
1402                         $ssUpdate->doUpdate();
1403                 }
1404                 $status = Status::newGood();
1405
1406                 if( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) {
1407                         $this->subscribeToMediaWikiAnnounce( $status );
1408                 }
1409
1410                 return $status;
1411         }
1412
1413         private function subscribeToMediaWikiAnnounce( Status $s ) {
1414                 $params = array(
1415                         'email'    => $this->getVar( '_AdminEmail' ),
1416                         'language' => 'en',
1417                         'digest'   => 0
1418                 );
1419
1420                 // Mailman doesn't support as many languages as we do, so check to make
1421                 // sure their selected language is available
1422                 $myLang = $this->getVar( '_UserLang' );
1423                 if( in_array( $myLang, $this->mediaWikiAnnounceLanguages ) ) {
1424                         $myLang = $myLang == 'pt-br' ? 'pt_BR' : $myLang; // rewrite to Mailman's pt_BR
1425                         $params['language'] = $myLang;
1426                 }
1427
1428                 if( MWHttpRequest::canMakeRequests() ) {
1429                         $res = MWHttpRequest::factory( $this->mediaWikiAnnounceUrl,
1430                                 array( 'method' => 'POST', 'postData' => $params ) )->execute();
1431                         if( !$res->isOK() ) {
1432                                 $s->warning( 'config-install-subscribe-fail', $res->getMessage() );
1433                         }
1434                 } else {
1435                         $s->warning( 'config-install-subscribe-notpossible' );
1436                 }
1437         }
1438
1439         /**
1440          * Insert Main Page with default content.
1441          *
1442          * @return Status
1443          */
1444         protected function createMainpage( DatabaseInstaller $installer ) {
1445                 $status = Status::newGood();
1446                 try {
1447                         $article = new Article( Title::newMainPage() );
1448                         $article->doEdit( wfMsgForContent( 'mainpagetext' ) . "\n\n" .
1449                                                                 wfMsgForContent( 'mainpagedocfooter' ),
1450                                                                 '',
1451                                                                 EDIT_NEW,
1452                                                                 false,
1453                                                                 User::newFromName( 'MediaWiki default' ) );
1454                 } catch (MWException $e) {
1455                         //using raw, because $wgShowExceptionDetails can not be set yet
1456                         $status->fatal( 'config-install-mainpage-failed', $e->getMessage() );
1457                 }
1458
1459                 return $status;
1460         }
1461
1462         /**
1463          * Override the necessary bits of the config to run an installation.
1464          */
1465         public static function overrideConfig() {
1466                 define( 'MW_NO_SESSION', 1 );
1467
1468                 // Don't access the database
1469                 $GLOBALS['wgUseDatabaseMessages'] = false;
1470                 // Debug-friendly
1471                 $GLOBALS['wgShowExceptionDetails'] = true;
1472                 // Don't break forms
1473                 $GLOBALS['wgExternalLinkTarget'] = '_blank';
1474
1475                 // Extended debugging
1476                 $GLOBALS['wgShowSQLErrors'] = true;
1477                 $GLOBALS['wgShowDBErrorBacktrace'] = true;
1478
1479                 // Allow multiple ob_flush() calls
1480                 $GLOBALS['wgDisableOutputCompression'] = true;
1481
1482                 // Use a sensible cookie prefix (not my_wiki)
1483                 $GLOBALS['wgCookiePrefix'] = 'mw_installer';
1484
1485                 // Some of the environment checks make shell requests, remove limits
1486                 $GLOBALS['wgMaxShellMemory'] = 0;
1487         }
1488
1489         /**
1490          * Add an installation step following the given step.
1491          *
1492          * @param $callback Array A valid installation callback array, in this form:
1493          *    array( 'name' => 'some-unique-name', 'callback' => array( $obj, 'function' ) );
1494          * @param $findStep String the step to find. Omit to put the step at the beginning
1495          */
1496         public function addInstallStep( $callback, $findStep = 'BEGINNING' ) {
1497                 $this->extraInstallSteps[$findStep][] = $callback;
1498         }
1499 }