0b7979dce419e09047fd3ec17d12d310c2e4d5c1
[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_admins' => 0,
608 'ss_images' => 0 ),
609 __METHOD__, 'IGNORE' );
610 return Status::newGood();
611 }
612
613 /**
614 * Exports all wg* variables stored by the installer into global scope.
615 */
616 public function exportVars() {
617 foreach ( $this->settings as $name => $value ) {
618 if ( substr( $name, 0, 2 ) == 'wg' ) {
619 $GLOBALS[$name] = $value;
620 }
621 }
622 }
623
624 /**
625 * Environment check for DB types.
626 * @return bool
627 */
628 protected function envCheckDB() {
629 global $wgLang;
630
631 $allNames = array();
632
633 foreach ( self::getDBTypes() as $name ) {
634 $allNames[] = wfMsg( "config-type-$name" );
635 }
636
637 // cache initially available databases to make sure that everything will be displayed correctly
638 // after a refresh on env checks page
639 $databases = $this->getVar( '_CompiledDBs-preFilter' );
640 if ( !$databases ) {
641 $databases = $this->getVar( '_CompiledDBs' );
642 $this->setVar( '_CompiledDBs-preFilter', $databases );
643 }
644
645 $databases = array_flip ( $databases );
646 foreach ( array_keys( $databases ) as $db ) {
647 $installer = $this->getDBInstaller( $db );
648 $status = $installer->checkPrerequisites();
649 if ( !$status->isGood() ) {
650 $this->showStatusMessage( $status );
651 }
652 if ( !$status->isOK() ) {
653 unset( $databases[$db] );
654 }
655 }
656 $databases = array_flip( $databases );
657 if ( !$databases ) {
658 $this->showError( 'config-no-db', $wgLang->commaList( $allNames ) );
659 // @todo FIXME: This only works for the web installer!
660 return false;
661 }
662 $this->setVar( '_CompiledDBs', $databases );
663 }
664
665 /**
666 * Environment check for register_globals.
667 */
668 protected function envCheckRegisterGlobals() {
669 if( wfIniGetBool( 'register_globals' ) ) {
670 $this->showMessage( 'config-register-globals' );
671 }
672 }
673
674 /**
675 * Some versions of libxml+PHP break < and > encoding horribly
676 */
677 protected function envCheckBrokenXML() {
678 $test = new PhpXmlBugTester();
679 if ( !$test->ok ) {
680 $this->showError( 'config-brokenlibxml' );
681 return false;
682 }
683 }
684
685 /**
686 * Test PHP (probably 5.3.1, but it could regress again) to make sure that
687 * reference parameters to __call() are not converted to null
688 */
689 protected function envCheckPHP531() {
690 $test = new PhpRefCallBugTester;
691 $test->execute();
692 if ( !$test->ok ) {
693 $this->showError( 'config-using531', phpversion() );
694 return false;
695 }
696 }
697
698 /**
699 * Environment check for magic_quotes_runtime.
700 */
701 protected function envCheckMagicQuotes() {
702 if( wfIniGetBool( "magic_quotes_runtime" ) ) {
703 $this->showError( 'config-magic-quotes-runtime' );
704 return false;
705 }
706 }
707
708 /**
709 * Environment check for magic_quotes_sybase.
710 */
711 protected function envCheckMagicSybase() {
712 if ( wfIniGetBool( 'magic_quotes_sybase' ) ) {
713 $this->showError( 'config-magic-quotes-sybase' );
714 return false;
715 }
716 }
717
718 /**
719 * Environment check for mbstring.func_overload.
720 */
721 protected function envCheckMbstring() {
722 if ( wfIniGetBool( 'mbstring.func_overload' ) ) {
723 $this->showError( 'config-mbstring' );
724 return false;
725 }
726 }
727
728 /**
729 * Environment check for zend.ze1_compatibility_mode.
730 */
731 protected function envCheckZE1() {
732 if ( wfIniGetBool( 'zend.ze1_compatibility_mode' ) ) {
733 $this->showError( 'config-ze1' );
734 return false;
735 }
736 }
737
738 /**
739 * Environment check for safe_mode.
740 */
741 protected function envCheckSafeMode() {
742 if ( wfIniGetBool( 'safe_mode' ) ) {
743 $this->setVar( '_SafeMode', true );
744 $this->showMessage( 'config-safe-mode' );
745 }
746 }
747
748 /**
749 * Environment check for the XML module.
750 */
751 protected function envCheckXML() {
752 if ( !function_exists( "utf8_encode" ) ) {
753 $this->showError( 'config-xml-bad' );
754 return false;
755 }
756 }
757
758 /**
759 * Environment check for the PCRE module.
760 */
761 protected function envCheckPCRE() {
762 if ( !function_exists( 'preg_match' ) ) {
763 $this->showError( 'config-pcre' );
764 return false;
765 }
766 wfSuppressWarnings();
767 $regexd = preg_replace( '/[\x{0430}-\x{04FF}]/iu', '', '-АБВГД-' );
768 wfRestoreWarnings();
769 if ( $regexd != '--' ) {
770 $this->showError( 'config-pcre-no-utf8' );
771 return false;
772 }
773 }
774
775 /**
776 * Environment check for available memory.
777 */
778 protected function envCheckMemory() {
779 $limit = ini_get( 'memory_limit' );
780
781 if ( !$limit || $limit == -1 ) {
782 return true;
783 }
784
785 $n = wfShorthandToInteger( $limit );
786
787 if( $n < $this->minMemorySize * 1024 * 1024 ) {
788 $newLimit = "{$this->minMemorySize}M";
789
790 if( ini_set( "memory_limit", $newLimit ) === false ) {
791 $this->showMessage( 'config-memory-bad', $limit );
792 } else {
793 $this->showMessage( 'config-memory-raised', $limit, $newLimit );
794 $this->setVar( '_RaiseMemory', true );
795 }
796 } else {
797 return true;
798 }
799 }
800
801 /**
802 * Environment check for compiled object cache types.
803 */
804 protected function envCheckCache() {
805 $caches = array();
806 foreach ( $this->objectCaches as $name => $function ) {
807 if ( function_exists( $function ) ) {
808 if ( $name == 'xcache' && !wfIniGetBool( 'xcache.var_size' ) ) {
809 continue;
810 }
811 $caches[$name] = true;
812 }
813 }
814
815 if ( !$caches ) {
816 $this->showMessage( 'config-no-cache' );
817 }
818
819 $this->setVar( '_Caches', $caches );
820 }
821
822 /**
823 * Scare user to death if they have mod_security
824 */
825 protected function envCheckModSecurity() {
826 if ( self::apacheModulePresent( 'mod_security' ) ) {
827 $this->showMessage( 'config-mod-security' );
828 }
829 }
830
831 /**
832 * Search for GNU diff3.
833 */
834 protected function envCheckDiff3() {
835 $names = array( "gdiff3", "diff3", "diff3.exe" );
836 $versionInfo = array( '$1 --version 2>&1', 'GNU diffutils' );
837
838 $diff3 = self::locateExecutableInDefaultPaths( $names, $versionInfo );
839
840 if ( $diff3 ) {
841 $this->setVar( 'wgDiff3', $diff3 );
842 } else {
843 $this->setVar( 'wgDiff3', false );
844 $this->showMessage( 'config-diff3-bad' );
845 }
846 }
847
848 /**
849 * Environment check for ImageMagick and GD.
850 */
851 protected function envCheckGraphics() {
852 $names = array( wfIsWindows() ? 'convert.exe' : 'convert' );
853 $convert = self::locateExecutableInDefaultPaths( $names, array( '$1 -version', 'ImageMagick' ) );
854
855 $this->setVar( 'wgImageMagickConvertCommand', '' );
856 if ( $convert ) {
857 $this->setVar( 'wgImageMagickConvertCommand', $convert );
858 $this->showMessage( 'config-imagemagick', $convert );
859 return true;
860 } elseif ( function_exists( 'imagejpeg' ) ) {
861 $this->showMessage( 'config-gd' );
862 return true;
863 } else {
864 $this->showMessage( 'config-no-scaling' );
865 }
866 }
867
868 /**
869 * Environment check for the server hostname.
870 */
871 protected function envCheckServer() {
872 $server = $this->envGetDefaultServer();
873 $this->showMessage( 'config-using-server', $server );
874 $this->setVar( 'wgServer', $server );
875 }
876
877 /**
878 * Helper function to be called from envCheckServer()
879 * @return String
880 */
881 protected abstract function envGetDefaultServer();
882
883 /**
884 * Environment check for setting $IP and $wgScriptPath.
885 * @return bool
886 */
887 protected function envCheckPath() {
888 global $IP;
889 $IP = dirname( dirname( dirname( __FILE__ ) ) );
890 $this->setVar( 'IP', $IP );
891
892 $this->showMessage( 'config-using-uri', $this->getVar( 'wgServer' ), $this->getVar( 'wgScriptPath' ) );
893 return true;
894 }
895
896 /**
897 * Environment check for setting the preferred PHP file extension.
898 */
899 protected function envCheckExtension() {
900 // @todo FIXME: Detect this properly
901 if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) {
902 $ext = 'php5';
903 } else {
904 $ext = 'php';
905 }
906 $this->setVar( 'wgScriptExtension', ".$ext" );
907 }
908
909 /**
910 * TODO: document
911 * @return bool
912 */
913 protected function envCheckShellLocale() {
914 $os = php_uname( 's' );
915 $supported = array( 'Linux', 'SunOS', 'HP-UX', 'Darwin' ); # Tested these
916
917 if ( !in_array( $os, $supported ) ) {
918 return true;
919 }
920
921 # Get a list of available locales.
922 $ret = false;
923 $lines = wfShellExec( '/usr/bin/locale -a', $ret );
924
925 if ( $ret ) {
926 return true;
927 }
928
929 $lines = wfArrayMap( 'trim', explode( "\n", $lines ) );
930 $candidatesByLocale = array();
931 $candidatesByLang = array();
932
933 foreach ( $lines as $line ) {
934 if ( $line === '' ) {
935 continue;
936 }
937
938 if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) {
939 continue;
940 }
941
942 list( $all, $lang, $territory, $charset, $modifier ) = $m;
943
944 $candidatesByLocale[$m[0]] = $m;
945 $candidatesByLang[$lang][] = $m;
946 }
947
948 # Try the current value of LANG.
949 if ( isset( $candidatesByLocale[ getenv( 'LANG' ) ] ) ) {
950 $this->setVar( 'wgShellLocale', getenv( 'LANG' ) );
951 return true;
952 }
953
954 # Try the most common ones.
955 $commonLocales = array( 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' );
956 foreach ( $commonLocales as $commonLocale ) {
957 if ( isset( $candidatesByLocale[$commonLocale] ) ) {
958 $this->setVar( 'wgShellLocale', $commonLocale );
959 return true;
960 }
961 }
962
963 # Is there an available locale in the Wiki's language?
964 $wikiLang = $this->getVar( 'wgLanguageCode' );
965
966 if ( isset( $candidatesByLang[$wikiLang] ) ) {
967 $m = reset( $candidatesByLang[$wikiLang] );
968 $this->setVar( 'wgShellLocale', $m[0] );
969 return true;
970 }
971
972 # Are there any at all?
973 if ( count( $candidatesByLocale ) ) {
974 $m = reset( $candidatesByLocale );
975 $this->setVar( 'wgShellLocale', $m[0] );
976 return true;
977 }
978
979 # Give up.
980 return true;
981 }
982
983 /**
984 * TODO: document
985 */
986 protected function envCheckUploadsDirectory() {
987 global $IP;
988
989 $dir = $IP . '/images/';
990 $url = $this->getVar( 'wgServer' ) . $this->getVar( 'wgScriptPath' ) . '/images/';
991 $safe = !$this->dirIsExecutable( $dir, $url );
992
993 if ( $safe ) {
994 return true;
995 } else {
996 $this->showMessage( 'config-uploads-not-safe', $dir );
997 }
998 }
999
1000 /**
1001 * Checks if suhosin.get.max_value_length is set, and if so, sets
1002 * $wgResourceLoaderMaxQueryLength to that value in the generated
1003 * LocalSettings file
1004 */
1005 protected function envCheckSuhosinMaxValueLength() {
1006 $maxValueLength = ini_get( 'suhosin.get.max_value_length' );
1007 if ( $maxValueLength > 0 ) {
1008 if( $maxValueLength < 1024 ) {
1009 # Only warn if the value is below the sane 1024
1010 $this->showMessage( 'config-suhosin-max-value-length', $maxValueLength );
1011 }
1012 } else {
1013 $maxValueLength = -1;
1014 }
1015 $this->setVar( 'wgResourceLoaderMaxQueryLength', $maxValueLength );
1016 }
1017
1018 /**
1019 * Convert a hex string representing a Unicode code point to that code point.
1020 * @param $c String
1021 * @return string
1022 */
1023 protected function unicodeChar( $c ) {
1024 $c = hexdec($c);
1025 if ($c <= 0x7F) {
1026 return chr($c);
1027 } elseif ($c <= 0x7FF) {
1028 return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F);
1029 } elseif ($c <= 0xFFFF) {
1030 return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F)
1031 . chr(0x80 | $c & 0x3F);
1032 } elseif ($c <= 0x10FFFF) {
1033 return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F)
1034 . chr(0x80 | $c >> 6 & 0x3F)
1035 . chr(0x80 | $c & 0x3F);
1036 } else {
1037 return false;
1038 }
1039 }
1040
1041
1042 /**
1043 * Check the libicu version
1044 */
1045 protected function envCheckLibicu() {
1046 $utf8 = function_exists( 'utf8_normalize' );
1047 $intl = function_exists( 'normalizer_normalize' );
1048
1049 /**
1050 * This needs to be updated something that the latest libicu
1051 * will properly normalize. This normalization was found at
1052 * http://www.unicode.org/versions/Unicode5.2.0/#Character_Additions
1053 * Note that we use the hex representation to create the code
1054 * points in order to avoid any Unicode-destroying during transit.
1055 */
1056 $not_normal_c = $this->unicodeChar("FA6C");
1057 $normal_c = $this->unicodeChar("242EE");
1058
1059 $useNormalizer = 'php';
1060 $needsUpdate = false;
1061
1062 /**
1063 * We're going to prefer the pecl extension here unless
1064 * utf8_normalize is more up to date.
1065 */
1066 if( $utf8 ) {
1067 $useNormalizer = 'utf8';
1068 $utf8 = utf8_normalize( $not_normal_c, UtfNormal::UNORM_NFC );
1069 if ( $utf8 !== $normal_c ) $needsUpdate = true;
1070 }
1071 if( $intl ) {
1072 $useNormalizer = 'intl';
1073 $intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C );
1074 if ( $intl !== $normal_c ) $needsUpdate = true;
1075 }
1076
1077 // Uses messages 'config-unicode-using-php', 'config-unicode-using-utf8', 'config-unicode-using-intl'
1078 if( $useNormalizer === 'php' ) {
1079 $this->showMessage( 'config-unicode-pure-php-warning' );
1080 } else {
1081 $this->showMessage( 'config-unicode-using-' . $useNormalizer );
1082 if( $needsUpdate ) {
1083 $this->showMessage( 'config-unicode-update-warning' );
1084 }
1085 }
1086 }
1087
1088 protected function envCheckCtype() {
1089 if ( !function_exists( 'ctype_digit' ) ) {
1090 $this->showError( 'config-ctype' );
1091 return false;
1092 }
1093 }
1094
1095 /**
1096 * Get an array of likely places we can find executables. Check a bunch
1097 * of known Unix-like defaults, as well as the PATH environment variable
1098 * (which should maybe make it work for Windows?)
1099 *
1100 * @return Array
1101 */
1102 protected static function getPossibleBinPaths() {
1103 return array_merge(
1104 array( '/usr/bin', '/usr/local/bin', '/opt/csw/bin',
1105 '/usr/gnu/bin', '/usr/sfw/bin', '/sw/bin', '/opt/local/bin' ),
1106 explode( PATH_SEPARATOR, getenv( 'PATH' ) )
1107 );
1108 }
1109
1110 /**
1111 * Search a path for any of the given executable names. Returns the
1112 * executable name if found. Also checks the version string returned
1113 * by each executable.
1114 *
1115 * Used only by environment checks.
1116 *
1117 * @param $path String: path to search
1118 * @param $names Array of executable names
1119 * @param $versionInfo Boolean false or array with two members:
1120 * 0 => Command to run for version check, with $1 for the full executable name
1121 * 1 => String to compare the output with
1122 *
1123 * If $versionInfo is not false, only executables with a version
1124 * matching $versionInfo[1] will be returned.
1125 */
1126 public static function locateExecutable( $path, $names, $versionInfo = false ) {
1127 if ( !is_array( $names ) ) {
1128 $names = array( $names );
1129 }
1130
1131 foreach ( $names as $name ) {
1132 $command = $path . DIRECTORY_SEPARATOR . $name;
1133
1134 wfSuppressWarnings();
1135 $file_exists = file_exists( $command );
1136 wfRestoreWarnings();
1137
1138 if ( $file_exists ) {
1139 if ( !$versionInfo ) {
1140 return $command;
1141 }
1142
1143 $file = str_replace( '$1', wfEscapeShellArg( $command ), $versionInfo[0] );
1144 if ( strstr( wfShellExec( $file ), $versionInfo[1] ) !== false ) {
1145 return $command;
1146 }
1147 }
1148 }
1149 return false;
1150 }
1151
1152 /**
1153 * Same as locateExecutable(), but checks in getPossibleBinPaths() by default
1154 * @see locateExecutable()
1155 * @param $names
1156 * @param $versionInfo bool
1157 * @return bool|string
1158 */
1159 public static function locateExecutableInDefaultPaths( $names, $versionInfo = false ) {
1160 foreach( self::getPossibleBinPaths() as $path ) {
1161 $exe = self::locateExecutable( $path, $names, $versionInfo );
1162 if( $exe !== false ) {
1163 return $exe;
1164 }
1165 }
1166 return false;
1167 }
1168
1169 /**
1170 * Checks if scripts located in the given directory can be executed via the given URL.
1171 *
1172 * Used only by environment checks.
1173 */
1174 public function dirIsExecutable( $dir, $url ) {
1175 $scriptTypes = array(
1176 'php' => array(
1177 "<?php echo 'ex' . 'ec';",
1178 "#!/var/env php5\n<?php echo 'ex' . 'ec';",
1179 ),
1180 );
1181
1182 // it would be good to check other popular languages here, but it'll be slow.
1183
1184 wfSuppressWarnings();
1185
1186 foreach ( $scriptTypes as $ext => $contents ) {
1187 foreach ( $contents as $source ) {
1188 $file = 'exectest.' . $ext;
1189
1190 if ( !file_put_contents( $dir . $file, $source ) ) {
1191 break;
1192 }
1193
1194 try {
1195 $text = Http::get( $url . $file, array( 'timeout' => 3 ) );
1196 }
1197 catch( MWException $e ) {
1198 // Http::get throws with allow_url_fopen = false and no curl extension.
1199 $text = null;
1200 }
1201 unlink( $dir . $file );
1202
1203 if ( $text == 'exec' ) {
1204 wfRestoreWarnings();
1205 return $ext;
1206 }
1207 }
1208 }
1209
1210 wfRestoreWarnings();
1211
1212 return false;
1213 }
1214
1215 /**
1216 * Checks for presence of an Apache module. Works only if PHP is running as an Apache module, too.
1217 *
1218 * @param $moduleName String: Name of module to check.
1219 * @return bool
1220 */
1221 public static function apacheModulePresent( $moduleName ) {
1222 if ( function_exists( 'apache_get_modules' ) && in_array( $moduleName, apache_get_modules() ) ) {
1223 return true;
1224 }
1225 // try it the hard way
1226 ob_start();
1227 phpinfo( INFO_MODULES );
1228 $info = ob_get_clean();
1229 return strpos( $info, $moduleName ) !== false;
1230 }
1231
1232 /**
1233 * ParserOptions are constructed before we determined the language, so fix it
1234 *
1235 * @param $lang Language
1236 */
1237 public function setParserLanguage( $lang ) {
1238 $this->parserOptions->setTargetLanguage( $lang );
1239 $this->parserOptions->setUserLang( $lang );
1240 }
1241
1242 /**
1243 * Overridden by WebInstaller to provide lastPage parameters.
1244 * @param $page string
1245 * @return string
1246 */
1247 protected function getDocUrl( $page ) {
1248 return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page );
1249 }
1250
1251 /**
1252 * Finds extensions that follow the format /extensions/Name/Name.php,
1253 * and returns an array containing the value for 'Name' for each found extension.
1254 *
1255 * @return array
1256 */
1257 public function findExtensions() {
1258 if( $this->getVar( 'IP' ) === null ) {
1259 return false;
1260 }
1261
1262 $exts = array();
1263 $extDir = $this->getVar( 'IP' ) . '/extensions';
1264 $dh = opendir( $extDir );
1265
1266 while ( ( $file = readdir( $dh ) ) !== false ) {
1267 if( !is_dir( "$extDir/$file" ) ) {
1268 continue;
1269 }
1270 if( file_exists( "$extDir/$file/$file.php" ) ) {
1271 $exts[] = $file;
1272 }
1273 }
1274
1275 return $exts;
1276 }
1277
1278 /**
1279 * Installs the auto-detected extensions.
1280 *
1281 * @return Status
1282 */
1283 protected function includeExtensions() {
1284 global $IP;
1285 $exts = $this->getVar( '_Extensions' );
1286 $IP = $this->getVar( 'IP' );
1287
1288 /**
1289 * We need to include DefaultSettings before including extensions to avoid
1290 * warnings about unset variables. However, the only thing we really
1291 * want here is $wgHooks['LoadExtensionSchemaUpdates']. This won't work
1292 * if the extension has hidden hook registration in $wgExtensionFunctions,
1293 * but we're not opening that can of worms
1294 * @see https://bugzilla.wikimedia.org/show_bug.cgi?id=26857
1295 */
1296 global $wgAutoloadClasses;
1297 $wgAutoloadClasses = array();
1298
1299 require( "$IP/includes/DefaultSettings.php" );
1300
1301 foreach( $exts as $e ) {
1302 require_once( "$IP/extensions/$e/$e.php" );
1303 }
1304
1305 $hooksWeWant = isset( $wgHooks['LoadExtensionSchemaUpdates'] ) ?
1306 $wgHooks['LoadExtensionSchemaUpdates'] : array();
1307
1308 // Unset everyone else's hooks. Lord knows what someone might be doing
1309 // in ParserFirstCallInit (see bug 27171)
1310 $GLOBALS['wgHooks'] = array( 'LoadExtensionSchemaUpdates' => $hooksWeWant );
1311
1312 return Status::newGood();
1313 }
1314
1315 /**
1316 * Get an array of install steps. Should always be in the format of
1317 * array(
1318 * 'name' => 'someuniquename',
1319 * 'callback' => array( $obj, 'method' ),
1320 * )
1321 * There must be a config-install-$name message defined per step, which will
1322 * be shown on install.
1323 *
1324 * @param $installer DatabaseInstaller so we can make callbacks
1325 * @return array
1326 */
1327 protected function getInstallSteps( DatabaseInstaller $installer ) {
1328 $coreInstallSteps = array(
1329 array( 'name' => 'database', 'callback' => array( $installer, 'setupDatabase' ) ),
1330 array( 'name' => 'tables', 'callback' => array( $installer, 'createTables' ) ),
1331 array( 'name' => 'interwiki', 'callback' => array( $installer, 'populateInterwikiTable' ) ),
1332 array( 'name' => 'stats', 'callback' => array( $this, 'populateSiteStats' ) ),
1333 array( 'name' => 'keys', 'callback' => array( $this, 'generateKeys' ) ),
1334 array( 'name' => 'sysop', 'callback' => array( $this, 'createSysop' ) ),
1335 array( 'name' => 'mainpage', 'callback' => array( $this, 'createMainpage' ) ),
1336 );
1337
1338 // Build the array of install steps starting from the core install list,
1339 // then adding any callbacks that wanted to attach after a given step
1340 foreach( $coreInstallSteps as $step ) {
1341 $this->installSteps[] = $step;
1342 if( isset( $this->extraInstallSteps[ $step['name'] ] ) ) {
1343 $this->installSteps = array_merge(
1344 $this->installSteps,
1345 $this->extraInstallSteps[ $step['name'] ]
1346 );
1347 }
1348 }
1349
1350 // Prepend any steps that want to be at the beginning
1351 if( isset( $this->extraInstallSteps['BEGINNING'] ) ) {
1352 $this->installSteps = array_merge(
1353 $this->extraInstallSteps['BEGINNING'],
1354 $this->installSteps
1355 );
1356 }
1357
1358 // Extensions should always go first, chance to tie into hooks and such
1359 if( count( $this->getVar( '_Extensions' ) ) ) {
1360 array_unshift( $this->installSteps,
1361 array( 'name' => 'extensions', 'callback' => array( $this, 'includeExtensions' ) )
1362 );
1363 $this->installSteps[] = array(
1364 'name' => 'extension-tables',
1365 'callback' => array( $installer, 'createExtensionTables' )
1366 );
1367 }
1368 return $this->installSteps;
1369 }
1370
1371 /**
1372 * Actually perform the installation.
1373 *
1374 * @param $startCB Array A callback array for the beginning of each step
1375 * @param $endCB Array A callback array for the end of each step
1376 *
1377 * @return Array of Status objects
1378 */
1379 public function performInstallation( $startCB, $endCB ) {
1380 $installResults = array();
1381 $installer = $this->getDBInstaller();
1382 $installer->preInstall();
1383 $steps = $this->getInstallSteps( $installer );
1384 foreach( $steps as $stepObj ) {
1385 $name = $stepObj['name'];
1386 call_user_func_array( $startCB, array( $name ) );
1387
1388 // Perform the callback step
1389 $status = call_user_func( $stepObj['callback'], $installer );
1390
1391 // Output and save the results
1392 call_user_func( $endCB, $name, $status );
1393 $installResults[$name] = $status;
1394
1395 // If we've hit some sort of fatal, we need to bail.
1396 // Callback already had a chance to do output above.
1397 if( !$status->isOk() ) {
1398 break;
1399 }
1400 }
1401 if( $status->isOk() ) {
1402 $this->setVar( '_InstallDone', true );
1403 }
1404 return $installResults;
1405 }
1406
1407 /**
1408 * Generate $wgSecretKey. Will warn if we had to use mt_rand() instead of
1409 * /dev/urandom
1410 *
1411 * @return Status
1412 */
1413 public function generateKeys() {
1414 $keys = array( 'wgSecretKey' => 64 );
1415 if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) {
1416 $keys['wgUpgradeKey'] = 16;
1417 }
1418 return $this->doGenerateKeys( $keys );
1419 }
1420
1421 /**
1422 * Generate a secret value for variables using either
1423 * /dev/urandom or mt_rand(). Produce a warning in the later case.
1424 *
1425 * @param $keys Array
1426 * @return Status
1427 */
1428 protected function doGenerateKeys( $keys ) {
1429 $status = Status::newGood();
1430
1431 wfSuppressWarnings();
1432 $file = fopen( "/dev/urandom", "r" );
1433 wfRestoreWarnings();
1434
1435 foreach ( $keys as $name => $length ) {
1436 if ( $file ) {
1437 $secretKey = bin2hex( fread( $file, $length / 2 ) );
1438 } else {
1439 $secretKey = '';
1440
1441 for ( $i = 0; $i < $length / 8; $i++ ) {
1442 $secretKey .= dechex( mt_rand( 0, 0x7fffffff ) );
1443 }
1444 }
1445
1446 $this->setVar( $name, $secretKey );
1447 }
1448
1449 if ( $file ) {
1450 fclose( $file );
1451 } else {
1452 $names = array_keys ( $keys );
1453 $names = preg_replace( '/^(.*)$/', '\$$1', $names );
1454 global $wgLang;
1455 $status->warning( 'config-insecure-keys', $wgLang->listToText( $names ), count( $names ) );
1456 }
1457
1458 return $status;
1459 }
1460
1461 /**
1462 * Create the first user account, grant it sysop and bureaucrat rights
1463 *
1464 * @return Status
1465 */
1466 protected function createSysop() {
1467 $name = $this->getVar( '_AdminName' );
1468 $user = User::newFromName( $name );
1469
1470 if ( !$user ) {
1471 // We should've validated this earlier anyway!
1472 return Status::newFatal( 'config-admin-error-user', $name );
1473 }
1474
1475 if ( $user->idForName() == 0 ) {
1476 $user->addToDatabase();
1477
1478 try {
1479 $user->setPassword( $this->getVar( '_AdminPassword' ) );
1480 } catch( PasswordError $pwe ) {
1481 return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() );
1482 }
1483
1484 $user->addGroup( 'sysop' );
1485 $user->addGroup( 'bureaucrat' );
1486 if( $this->getVar( '_AdminEmail' ) ) {
1487 $user->setEmail( $this->getVar( '_AdminEmail' ) );
1488 }
1489 $user->saveSettings();
1490
1491 // Update user count
1492 $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 );
1493 $ssUpdate->doUpdate();
1494 }
1495 $status = Status::newGood();
1496
1497 if( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) {
1498 $this->subscribeToMediaWikiAnnounce( $status );
1499 }
1500
1501 return $status;
1502 }
1503
1504 /**
1505 * @param $s Status
1506 */
1507 private function subscribeToMediaWikiAnnounce( Status $s ) {
1508 $params = array(
1509 'email' => $this->getVar( '_AdminEmail' ),
1510 'language' => 'en',
1511 'digest' => 0
1512 );
1513
1514 // Mailman doesn't support as many languages as we do, so check to make
1515 // sure their selected language is available
1516 $myLang = $this->getVar( '_UserLang' );
1517 if( in_array( $myLang, $this->mediaWikiAnnounceLanguages ) ) {
1518 $myLang = $myLang == 'pt-br' ? 'pt_BR' : $myLang; // rewrite to Mailman's pt_BR
1519 $params['language'] = $myLang;
1520 }
1521
1522 if( MWHttpRequest::canMakeRequests() ) {
1523 $res = MWHttpRequest::factory( $this->mediaWikiAnnounceUrl,
1524 array( 'method' => 'POST', 'postData' => $params ) )->execute();
1525 if( !$res->isOK() ) {
1526 $s->warning( 'config-install-subscribe-fail', $res->getMessage() );
1527 }
1528 } else {
1529 $s->warning( 'config-install-subscribe-notpossible' );
1530 }
1531 }
1532
1533 /**
1534 * Insert Main Page with default content.
1535 *
1536 * @param $installer DatabaseInstaller
1537 * @return Status
1538 */
1539 protected function createMainpage( DatabaseInstaller $installer ) {
1540 $status = Status::newGood();
1541 try {
1542 $page = WikiPage::factory( Title::newMainPage() );
1543 $page->doEdit( wfMsgForContent( 'mainpagetext' ) . "\n\n" .
1544 wfMsgForContent( 'mainpagedocfooter' ),
1545 '',
1546 EDIT_NEW,
1547 false,
1548 User::newFromName( 'MediaWiki default' ) );
1549 } catch (MWException $e) {
1550 //using raw, because $wgShowExceptionDetails can not be set yet
1551 $status->fatal( 'config-install-mainpage-failed', $e->getMessage() );
1552 }
1553
1554 return $status;
1555 }
1556
1557 /**
1558 * Override the necessary bits of the config to run an installation.
1559 */
1560 public static function overrideConfig() {
1561 define( 'MW_NO_SESSION', 1 );
1562
1563 // Don't access the database
1564 $GLOBALS['wgUseDatabaseMessages'] = false;
1565 // Debug-friendly
1566 $GLOBALS['wgShowExceptionDetails'] = true;
1567 // Don't break forms
1568 $GLOBALS['wgExternalLinkTarget'] = '_blank';
1569
1570 // Extended debugging
1571 $GLOBALS['wgShowSQLErrors'] = true;
1572 $GLOBALS['wgShowDBErrorBacktrace'] = true;
1573
1574 // Allow multiple ob_flush() calls
1575 $GLOBALS['wgDisableOutputCompression'] = true;
1576
1577 // Use a sensible cookie prefix (not my_wiki)
1578 $GLOBALS['wgCookiePrefix'] = 'mw_installer';
1579
1580 // Some of the environment checks make shell requests, remove limits
1581 $GLOBALS['wgMaxShellMemory'] = 0;
1582 }
1583
1584 /**
1585 * Add an installation step following the given step.
1586 *
1587 * @param $callback Array A valid installation callback array, in this form:
1588 * array( 'name' => 'some-unique-name', 'callback' => array( $obj, 'function' ) );
1589 * @param $findStep String the step to find. Omit to put the step at the beginning
1590 */
1591 public function addInstallStep( $callback, $findStep = 'BEGINNING' ) {
1592 $this->extraInstallSteps[$findStep][] = $callback;
1593 }
1594
1595 /**
1596 * Disable the time limit for execution.
1597 * Some long-running pages (Install, Upgrade) will want to do this
1598 */
1599 protected function disableTimeLimit() {
1600 wfSuppressWarnings();
1601 set_time_limit( 0 );
1602 wfRestoreWarnings();
1603 }
1604 }