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