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