Rm debugging hack from r94029.
[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 );
111
112 /**
113 * MediaWiki configuration globals that will eventually be passed through
114 * to LocalSettings.php. The names only are given here, the defaults
115 * typically come from DefaultSettings.php.
116 *
117 * @var array
118 */
119 protected $defaultVarNames = array(
120 'wgSitename',
121 'wgPasswordSender',
122 'wgLanguageCode',
123 'wgRightsIcon',
124 'wgRightsText',
125 'wgRightsUrl',
126 'wgMainCacheType',
127 'wgEnableEmail',
128 'wgEnableUserEmail',
129 'wgEnotifUserTalk',
130 'wgEnotifWatchlist',
131 'wgEmailAuthentication',
132 'wgDBtype',
133 'wgDiff3',
134 'wgImageMagickConvertCommand',
135 'IP',
136 'wgServer',
137 'wgScriptPath',
138 'wgScriptExtension',
139 'wgMetaNamespace',
140 'wgDeletedDirectory',
141 'wgEnableUploads',
142 'wgLogo',
143 'wgShellLocale',
144 'wgSecretKey',
145 'wgUseInstantCommons',
146 'wgUpgradeKey',
147 'wgDefaultSkin',
148 'wgResourceLoaderMaxQueryLength',
149 );
150
151 /**
152 * Variables that are stored alongside globals, and are used for any
153 * configuration of the installation process aside from the MediaWiki
154 * configuration. Map of names to defaults.
155 *
156 * @var array
157 */
158 protected $internalDefaults = array(
159 '_UserLang' => 'en',
160 '_Environment' => false,
161 '_CompiledDBs' => array(),
162 '_SafeMode' => false,
163 '_RaiseMemory' => false,
164 '_UpgradeDone' => false,
165 '_InstallDone' => false,
166 '_Caches' => array(),
167 '_InstallPassword' => '',
168 '_SameAccount' => true,
169 '_CreateDBAccount' => false,
170 '_NamespaceType' => 'site-name',
171 '_AdminName' => '', // will be set later, when the user selects language
172 '_AdminPassword' => '',
173 '_AdminPassword2' => '',
174 '_AdminEmail' => '',
175 '_Subscribe' => false,
176 '_SkipOptional' => 'continue',
177 '_RightsProfile' => 'wiki',
178 '_LicenseCode' => 'none',
179 '_CCDone' => false,
180 '_Extensions' => array(),
181 '_MemCachedServers' => '',
182 '_UpgradeKeySupplied' => false,
183 '_ExistingDBSettings' => false,
184 );
185
186 /**
187 * The actual list of installation steps. This will be initialized by getInstallSteps()
188 *
189 * @var array
190 */
191 private $installSteps = array();
192
193 /**
194 * Extra steps for installation, for things like DatabaseInstallers to modify
195 *
196 * @var array
197 */
198 protected $extraInstallSteps = array();
199
200 /**
201 * Known object cache types and the functions used to test for their existence.
202 *
203 * @var array
204 */
205 protected $objectCaches = array(
206 'xcache' => 'xcache_get',
207 'apc' => 'apc_fetch',
208 'eaccel' => 'eaccelerator_get',
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 */
300 public abstract function showMessage( $msg /*, ... */ );
301
302 /**
303 * Same as showMessage(), but for displaying errors
304 */
305 public abstract function showError( $msg /*, ... */ );
306
307 /**
308 * Show a message to the installing user by using a Status object
309 * @param $status Status
310 */
311 public abstract function showStatusMessage( Status $status );
312
313 /**
314 * Constructor, always call this from child classes.
315 */
316 public function __construct() {
317 global $wgExtensionMessagesFiles, $wgUser;
318
319 // Disable the i18n cache and LoadBalancer
320 Language::getLocalisationCache()->disableBackend();
321 LBFactory::disableBackend();
322
323 // Load the installer's i18n file.
324 $wgExtensionMessagesFiles['MediawikiInstaller'] =
325 dirname( __FILE__ ) . '/Installer.i18n.php';
326
327 // Having a user with id = 0 safeguards us from DB access via User::loadOptions().
328 $wgUser = User::newFromId( 0 );
329
330 $this->settings = $this->internalDefaults;
331
332 foreach ( $this->defaultVarNames as $var ) {
333 $this->settings[$var] = $GLOBALS[$var];
334 }
335
336 foreach ( self::getDBTypes() as $type ) {
337 $installer = $this->getDBInstaller( $type );
338
339 if ( !$installer->isCompiled() ) {
340 continue;
341 }
342
343 $defaults = $installer->getGlobalDefaults();
344
345 foreach ( $installer->getGlobalNames() as $var ) {
346 if ( isset( $defaults[$var] ) ) {
347 $this->settings[$var] = $defaults[$var];
348 } else {
349 $this->settings[$var] = $GLOBALS[$var];
350 }
351 }
352 }
353
354 $this->parserTitle = Title::newFromText( 'Installer' );
355 $this->parserOptions = new ParserOptions; // language will be wrong :(
356 $this->parserOptions->setEditSection( false );
357 }
358
359 /**
360 * Get a list of known DB types.
361 *
362 * @return array
363 */
364 public static function getDBTypes() {
365 return self::$dbTypes;
366 }
367
368 /**
369 * Do initial checks of the PHP environment. Set variables according to
370 * the observed environment.
371 *
372 * It's possible that this may be called under the CLI SAPI, not the SAPI
373 * that the wiki will primarily run under. In that case, the subclass should
374 * initialise variables such as wgScriptPath, before calling this function.
375 *
376 * Under the web subclass, it can already be assumed that PHP 5+ is in use
377 * and that sessions are working.
378 *
379 * @return Status
380 */
381 public function doEnvironmentChecks() {
382 $phpVersion = phpversion();
383 if( version_compare( $phpVersion, self::MINIMUM_PHP_VERSION, '>=' ) ) {
384 $this->showMessage( 'config-env-php', $phpVersion );
385 $good = true;
386 } else {
387 $this->showMessage( 'config-env-php-toolow', $phpVersion, self::MINIMUM_PHP_VERSION );
388 $good = false;
389 }
390
391 if( $good ) {
392 foreach ( $this->envChecks as $check ) {
393 $status = $this->$check();
394 if ( $status === false ) {
395 $good = false;
396 }
397 }
398 }
399
400 $this->setVar( '_Environment', $good );
401
402 return $good ? Status::newGood() : Status::newFatal( 'config-env-bad' );
403 }
404
405 /**
406 * Set a MW configuration variable, or internal installer configuration variable.
407 *
408 * @param $name String
409 * @param $value Mixed
410 */
411 public function setVar( $name, $value ) {
412 $this->settings[$name] = $value;
413 }
414
415 /**
416 * Get an MW configuration variable, or internal installer configuration variable.
417 * The defaults come from $GLOBALS (ultimately DefaultSettings.php).
418 * Installer variables are typically prefixed by an underscore.
419 *
420 * @param $name String
421 * @param $default Mixed
422 *
423 * @return mixed
424 */
425 public function getVar( $name, $default = null ) {
426 if ( !isset( $this->settings[$name] ) ) {
427 return $default;
428 } else {
429 return $this->settings[$name];
430 }
431 }
432
433 /**
434 * Get an instance of DatabaseInstaller for the specified DB type.
435 *
436 * @param $type Mixed: DB installer for which is needed, false to use default.
437 *
438 * @return DatabaseInstaller
439 */
440 public function getDBInstaller( $type = false ) {
441 if ( !$type ) {
442 $type = $this->getVar( 'wgDBtype' );
443 }
444
445 $type = strtolower( $type );
446
447 if ( !isset( $this->dbInstallers[$type] ) ) {
448 $class = ucfirst( $type ). 'Installer';
449 $this->dbInstallers[$type] = new $class( $this );
450 }
451
452 return $this->dbInstallers[$type];
453 }
454
455 /**
456 * Determine if LocalSettings.php exists. If it does, return its variables,
457 * merged with those from AdminSettings.php, as an array.
458 *
459 * @return Array
460 */
461 public static function getExistingLocalSettings() {
462 global $IP;
463
464 wfSuppressWarnings();
465 $_lsExists = file_exists( "$IP/LocalSettings.php" );
466 wfRestoreWarnings();
467
468 if( !$_lsExists ) {
469 return false;
470 }
471 unset($_lsExists);
472
473 require( "$IP/includes/DefaultSettings.php" );
474 require( "$IP/LocalSettings.php" );
475 if ( file_exists( "$IP/AdminSettings.php" ) ) {
476 require( "$IP/AdminSettings.php" );
477 }
478 return get_defined_vars();
479 }
480
481 /**
482 * Get a fake password for sending back to the user in HTML.
483 * This is a security mechanism to avoid compromise of the password in the
484 * event of session ID compromise.
485 *
486 * @param $realPassword String
487 *
488 * @return string
489 */
490 public function getFakePassword( $realPassword ) {
491 return str_repeat( '*', strlen( $realPassword ) );
492 }
493
494 /**
495 * Set a variable which stores a password, except if the new value is a
496 * fake password in which case leave it as it is.
497 *
498 * @param $name String
499 * @param $value Mixed
500 */
501 public function setPassword( $name, $value ) {
502 if ( !preg_match( '/^\*+$/', $value ) ) {
503 $this->setVar( $name, $value );
504 }
505 }
506
507 /**
508 * On POSIX systems return the primary group of the webserver we're running under.
509 * On other systems just returns null.
510 *
511 * This is used to advice the user that he should chgrp his mw-config/data/images directory as the
512 * webserver user before he can install.
513 *
514 * Public because SqliteInstaller needs it, and doesn't subclass Installer.
515 *
516 * @return mixed
517 */
518 public static function maybeGetWebserverPrimaryGroup() {
519 if ( !function_exists( 'posix_getegid' ) || !function_exists( 'posix_getpwuid' ) ) {
520 # I don't know this, this isn't UNIX.
521 return null;
522 }
523
524 # posix_getegid() *not* getmygid() because we want the group of the webserver,
525 # not whoever owns the current script.
526 $gid = posix_getegid();
527 $getpwuid = posix_getpwuid( $gid );
528 $group = $getpwuid['name'];
529
530 return $group;
531 }
532
533 /**
534 * Convert wikitext $text to HTML.
535 *
536 * This is potentially error prone since many parser features require a complete
537 * installed MW database. The solution is to just not use those features when you
538 * write your messages. This appears to work well enough. Basic formatting and
539 * external links work just fine.
540 *
541 * But in case a translator decides to throw in a #ifexist or internal link or
542 * whatever, this function is guarded to catch the attempted DB access and to present
543 * some fallback text.
544 *
545 * @param $text String
546 * @param $lineStart Boolean
547 * @return String
548 */
549 public function parse( $text, $lineStart = false ) {
550 global $wgParser;
551
552 try {
553 $out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
554 $html = $out->getText();
555 } catch ( DBAccessError $e ) {
556 $html = '<!--DB access attempted during parse--> ' . htmlspecialchars( $text );
557
558 if ( !empty( $this->debug ) ) {
559 $html .= "<!--\n" . $e->getTraceAsString() . "\n-->";
560 }
561 }
562
563 return $html;
564 }
565
566 /**
567 * @return ParserOptions
568 */
569 public function getParserOptions() {
570 return $this->parserOptions;
571 }
572
573 public function disableLinkPopups() {
574 $this->parserOptions->setExternalLinkTarget( false );
575 }
576
577 public function restoreLinkPopups() {
578 global $wgExternalLinkTarget;
579 $this->parserOptions->setExternalLinkTarget( $wgExternalLinkTarget );
580 }
581
582 /**
583 * Install step which adds a row to the site_stats table with appropriate
584 * initial values.
585 *
586 * @param $installer DatabaseInstaller
587 *
588 * @return Status
589 */
590 public function populateSiteStats( DatabaseInstaller $installer ) {
591 $status = $installer->getConnection();
592 if ( !$status->isOK() ) {
593 return $status;
594 }
595 $status->value->insert( 'site_stats', array(
596 'ss_row_id' => 1,
597 'ss_total_views' => 0,
598 'ss_total_edits' => 0,
599 'ss_good_articles' => 0,
600 'ss_total_pages' => 0,
601 'ss_users' => 0,
602 'ss_admins' => 0,
603 'ss_images' => 0 ),
604 __METHOD__, 'IGNORE' );
605 return Status::newGood();
606 }
607
608 /**
609 * Exports all wg* variables stored by the installer into global scope.
610 */
611 public function exportVars() {
612 foreach ( $this->settings as $name => $value ) {
613 if ( substr( $name, 0, 2 ) == 'wg' ) {
614 $GLOBALS[$name] = $value;
615 }
616 }
617 }
618
619 /**
620 * Environment check for DB types.
621 */
622 protected function envCheckDB() {
623 global $wgLang;
624
625 $compiledDBs = array();
626 $allNames = array();
627
628 foreach ( self::getDBTypes() as $name ) {
629 if ( $this->getDBInstaller( $name )->isCompiled() ) {
630 $compiledDBs[] = $name;
631 }
632 $allNames[] = wfMsg( 'config-type-' . $name );
633 }
634
635 $this->setVar( '_CompiledDBs', $compiledDBs );
636
637 if ( !$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 = WebRequest::detectServer();
860 $this->showMessage( 'config-using-server', $server );
861 $this->setVar( 'wgServer', $server );
862 }
863
864 /**
865 * Environment check for setting $IP and $wgScriptPath.
866 */
867 protected function envCheckPath() {
868 global $IP;
869 $IP = dirname( dirname( dirname( __FILE__ ) ) );
870 $this->setVar( 'IP', $IP );
871
872 $this->showMessage( 'config-using-uri', $this->getVar( 'wgServer' ), $this->getVar( 'wgScriptPath' ) );
873 return true;
874 }
875
876 /**
877 * Environment check for setting the preferred PHP file extension.
878 */
879 protected function envCheckExtension() {
880 // @todo FIXME: Detect this properly
881 if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) {
882 $ext = 'php5';
883 } else {
884 $ext = 'php';
885 }
886 $this->setVar( 'wgScriptExtension', ".$ext" );
887 }
888
889 /**
890 * TODO: document
891 */
892 protected function envCheckShellLocale() {
893 $os = php_uname( 's' );
894 $supported = array( 'Linux', 'SunOS', 'HP-UX', 'Darwin' ); # Tested these
895
896 if ( !in_array( $os, $supported ) ) {
897 return true;
898 }
899
900 # Get a list of available locales.
901 $ret = false;
902 $lines = wfShellExec( '/usr/bin/locale -a', $ret );
903
904 if ( $ret ) {
905 return true;
906 }
907
908 $lines = wfArrayMap( 'trim', explode( "\n", $lines ) );
909 $candidatesByLocale = array();
910 $candidatesByLang = array();
911
912 foreach ( $lines as $line ) {
913 if ( $line === '' ) {
914 continue;
915 }
916
917 if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) {
918 continue;
919 }
920
921 list( $all, $lang, $territory, $charset, $modifier ) = $m;
922
923 $candidatesByLocale[$m[0]] = $m;
924 $candidatesByLang[$lang][] = $m;
925 }
926
927 # Try the current value of LANG.
928 if ( isset( $candidatesByLocale[ getenv( 'LANG' ) ] ) ) {
929 $this->setVar( 'wgShellLocale', getenv( 'LANG' ) );
930 return true;
931 }
932
933 # Try the most common ones.
934 $commonLocales = array( 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' );
935 foreach ( $commonLocales as $commonLocale ) {
936 if ( isset( $candidatesByLocale[$commonLocale] ) ) {
937 $this->setVar( 'wgShellLocale', $commonLocale );
938 return true;
939 }
940 }
941
942 # Is there an available locale in the Wiki's language?
943 $wikiLang = $this->getVar( 'wgLanguageCode' );
944
945 if ( isset( $candidatesByLang[$wikiLang] ) ) {
946 $m = reset( $candidatesByLang[$wikiLang] );
947 $this->setVar( 'wgShellLocale', $m[0] );
948 return true;
949 }
950
951 # Are there any at all?
952 if ( count( $candidatesByLocale ) ) {
953 $m = reset( $candidatesByLocale );
954 $this->setVar( 'wgShellLocale', $m[0] );
955 return true;
956 }
957
958 # Give up.
959 return true;
960 }
961
962 /**
963 * TODO: document
964 */
965 protected function envCheckUploadsDirectory() {
966 global $IP;
967
968 $dir = $IP . '/images/';
969 $url = $this->getVar( 'wgServer' ) . $this->getVar( 'wgScriptPath' ) . '/images/';
970 $safe = !$this->dirIsExecutable( $dir, $url );
971
972 if ( $safe ) {
973 return true;
974 } else {
975 $this->showMessage( 'config-uploads-not-safe', $dir );
976 }
977 }
978
979 /**
980 * Checks if suhosin.get.max_value_length is set, and if so, sets
981 * $wgResourceLoaderMaxQueryLength to that value in the generated
982 * LocalSettings file
983 */
984 protected function envCheckSuhosinMaxValueLength() {
985 $maxValueLength = ini_get( 'suhosin.get.max_value_length' );
986 if ( $maxValueLength > 0 ) {
987 $this->showMessage( 'config-suhosin-max-value-length', $maxValueLength );
988 } else {
989 $maxValueLength = -1;
990 }
991 $this->setVar( 'wgResourceLoaderMaxQueryLength', $maxValueLength );
992 }
993
994 /**
995 * Convert a hex string representing a Unicode code point to that code point.
996 * @param $c String
997 * @return string
998 */
999 protected function unicodeChar( $c ) {
1000 $c = hexdec($c);
1001 if ($c <= 0x7F) {
1002 return chr($c);
1003 } elseif ($c <= 0x7FF) {
1004 return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F);
1005 } elseif ($c <= 0xFFFF) {
1006 return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F)
1007 . chr(0x80 | $c & 0x3F);
1008 } elseif ($c <= 0x10FFFF) {
1009 return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F)
1010 . chr(0x80 | $c >> 6 & 0x3F)
1011 . chr(0x80 | $c & 0x3F);
1012 } else {
1013 return false;
1014 }
1015 }
1016
1017
1018 /**
1019 * Check the libicu version
1020 */
1021 protected function envCheckLibicu() {
1022 $utf8 = function_exists( 'utf8_normalize' );
1023 $intl = function_exists( 'normalizer_normalize' );
1024
1025 /**
1026 * This needs to be updated something that the latest libicu
1027 * will properly normalize. This normalization was found at
1028 * http://www.unicode.org/versions/Unicode5.2.0/#Character_Additions
1029 * Note that we use the hex representation to create the code
1030 * points in order to avoid any Unicode-destroying during transit.
1031 */
1032 $not_normal_c = $this->unicodeChar("FA6C");
1033 $normal_c = $this->unicodeChar("242EE");
1034
1035 $useNormalizer = 'php';
1036 $needsUpdate = false;
1037
1038 /**
1039 * We're going to prefer the pecl extension here unless
1040 * utf8_normalize is more up to date.
1041 */
1042 if( $utf8 ) {
1043 $useNormalizer = 'utf8';
1044 $utf8 = utf8_normalize( $not_normal_c, UtfNormal::UNORM_NFC );
1045 if ( $utf8 !== $normal_c ) $needsUpdate = true;
1046 }
1047 if( $intl ) {
1048 $useNormalizer = 'intl';
1049 $intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C );
1050 if ( $intl !== $normal_c ) $needsUpdate = true;
1051 }
1052
1053 // Uses messages 'config-unicode-using-php', 'config-unicode-using-utf8', 'config-unicode-using-intl'
1054 if( $useNormalizer === 'php' ) {
1055 $this->showMessage( 'config-unicode-pure-php-warning' );
1056 } else {
1057 $this->showMessage( 'config-unicode-using-' . $useNormalizer );
1058 if( $needsUpdate ) {
1059 $this->showMessage( 'config-unicode-update-warning' );
1060 }
1061 }
1062 }
1063
1064 /**
1065 * Get an array of likely places we can find executables. Check a bunch
1066 * of known Unix-like defaults, as well as the PATH environment variable
1067 * (which should maybe make it work for Windows?)
1068 *
1069 * @return Array
1070 */
1071 protected static function getPossibleBinPaths() {
1072 return array_merge(
1073 array( '/usr/bin', '/usr/local/bin', '/opt/csw/bin',
1074 '/usr/gnu/bin', '/usr/sfw/bin', '/sw/bin', '/opt/local/bin' ),
1075 explode( PATH_SEPARATOR, getenv( 'PATH' ) )
1076 );
1077 }
1078
1079 /**
1080 * Search a path for any of the given executable names. Returns the
1081 * executable name if found. Also checks the version string returned
1082 * by each executable.
1083 *
1084 * Used only by environment checks.
1085 *
1086 * @param $path String: path to search
1087 * @param $names Array of executable names
1088 * @param $versionInfo Boolean false or array with two members:
1089 * 0 => Command to run for version check, with $1 for the full executable name
1090 * 1 => String to compare the output with
1091 *
1092 * If $versionInfo is not false, only executables with a version
1093 * matching $versionInfo[1] will be returned.
1094 */
1095 public static function locateExecutable( $path, $names, $versionInfo = false ) {
1096 if ( !is_array( $names ) ) {
1097 $names = array( $names );
1098 }
1099
1100 foreach ( $names as $name ) {
1101 $command = $path . DIRECTORY_SEPARATOR . $name;
1102
1103 wfSuppressWarnings();
1104 $file_exists = file_exists( $command );
1105 wfRestoreWarnings();
1106
1107 if ( $file_exists ) {
1108 if ( !$versionInfo ) {
1109 return $command;
1110 }
1111
1112 $file = str_replace( '$1', wfEscapeShellArg( $command ), $versionInfo[0] );
1113 if ( strstr( wfShellExec( $file ), $versionInfo[1] ) !== false ) {
1114 return $command;
1115 }
1116 }
1117 }
1118 return false;
1119 }
1120
1121 /**
1122 * Same as locateExecutable(), but checks in getPossibleBinPaths() by default
1123 * @see locateExecutable()
1124 */
1125 public static function locateExecutableInDefaultPaths( $names, $versionInfo = false ) {
1126 foreach( self::getPossibleBinPaths() as $path ) {
1127 $exe = self::locateExecutable( $path, $names, $versionInfo );
1128 if( $exe !== false ) {
1129 return $exe;
1130 }
1131 }
1132 return false;
1133 }
1134
1135 /**
1136 * Checks if scripts located in the given directory can be executed via the given URL.
1137 *
1138 * Used only by environment checks.
1139 */
1140 public function dirIsExecutable( $dir, $url ) {
1141 $scriptTypes = array(
1142 'php' => array(
1143 "<?php echo 'ex' . 'ec';",
1144 "#!/var/env php5\n<?php echo 'ex' . 'ec';",
1145 ),
1146 );
1147
1148 // it would be good to check other popular languages here, but it'll be slow.
1149
1150 wfSuppressWarnings();
1151
1152 foreach ( $scriptTypes as $ext => $contents ) {
1153 foreach ( $contents as $source ) {
1154 $file = 'exectest.' . $ext;
1155
1156 if ( !file_put_contents( $dir . $file, $source ) ) {
1157 break;
1158 }
1159
1160 try {
1161 $text = Http::get( $url . $file, array( 'timeout' => 3 ) );
1162 }
1163 catch( MWException $e ) {
1164 // Http::get throws with allow_url_fopen = false and no curl extension.
1165 $text = null;
1166 }
1167 unlink( $dir . $file );
1168
1169 if ( $text == 'exec' ) {
1170 wfRestoreWarnings();
1171 return $ext;
1172 }
1173 }
1174 }
1175
1176 wfRestoreWarnings();
1177
1178 return false;
1179 }
1180
1181 /**
1182 * Checks for presence of an Apache module. Works only if PHP is running as an Apache module, too.
1183 *
1184 * @param $moduleName String: Name of module to check.
1185 * @return bool
1186 */
1187 public static function apacheModulePresent( $moduleName ) {
1188 if ( function_exists( 'apache_get_modules' ) && in_array( $moduleName, apache_get_modules() ) ) {
1189 return true;
1190 }
1191 // try it the hard way
1192 ob_start();
1193 phpinfo( INFO_MODULES );
1194 $info = ob_get_clean();
1195 return strpos( $info, $moduleName ) !== false;
1196 }
1197
1198 /**
1199 * ParserOptions are constructed before we determined the language, so fix it
1200 *
1201 * @param $lang Language
1202 */
1203 public function setParserLanguage( $lang ) {
1204 $this->parserOptions->setTargetLanguage( $lang );
1205 $this->parserOptions->setUserLang( $lang->getCode() );
1206 }
1207
1208 /**
1209 * Overridden by WebInstaller to provide lastPage parameters.
1210 */
1211 protected function getDocUrl( $page ) {
1212 return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page );
1213 }
1214
1215 /**
1216 * Finds extensions that follow the format /extensions/Name/Name.php,
1217 * and returns an array containing the value for 'Name' for each found extension.
1218 *
1219 * @return array
1220 */
1221 public function findExtensions() {
1222 if( $this->getVar( 'IP' ) === null ) {
1223 return false;
1224 }
1225
1226 $exts = array();
1227 $extDir = $this->getVar( 'IP' ) . '/extensions';
1228 $dh = opendir( $extDir );
1229
1230 while ( ( $file = readdir( $dh ) ) !== false ) {
1231 if( !is_dir( "$extDir/$file" ) ) {
1232 continue;
1233 }
1234 if( file_exists( "$extDir/$file/$file.php" ) ) {
1235 $exts[] = $file;
1236 }
1237 }
1238
1239 return $exts;
1240 }
1241
1242 /**
1243 * Installs the auto-detected extensions.
1244 *
1245 * @return Status
1246 */
1247 protected function includeExtensions() {
1248 global $IP;
1249 $exts = $this->getVar( '_Extensions' );
1250 $IP = $this->getVar( 'IP' );
1251
1252 /**
1253 * We need to include DefaultSettings before including extensions to avoid
1254 * warnings about unset variables. However, the only thing we really
1255 * want here is $wgHooks['LoadExtensionSchemaUpdates']. This won't work
1256 * if the extension has hidden hook registration in $wgExtensionFunctions,
1257 * but we're not opening that can of worms
1258 * @see https://bugzilla.wikimedia.org/show_bug.cgi?id=26857
1259 */
1260 global $wgAutoloadClasses;
1261 $wgAutoloadClasses = array();
1262
1263 require( "$IP/includes/DefaultSettings.php" );
1264
1265 foreach( $exts as $e ) {
1266 require_once( "$IP/extensions/$e/$e.php" );
1267 }
1268
1269 $hooksWeWant = isset( $wgHooks['LoadExtensionSchemaUpdates'] ) ?
1270 $wgHooks['LoadExtensionSchemaUpdates'] : array();
1271
1272 // Unset everyone else's hooks. Lord knows what someone might be doing
1273 // in ParserFirstCallInit (see bug 27171)
1274 $GLOBALS['wgHooks'] = array( 'LoadExtensionSchemaUpdates' => $hooksWeWant );
1275
1276 return Status::newGood();
1277 }
1278
1279 /**
1280 * Get an array of install steps. Should always be in the format of
1281 * array(
1282 * 'name' => 'someuniquename',
1283 * 'callback' => array( $obj, 'method' ),
1284 * )
1285 * There must be a config-install-$name message defined per step, which will
1286 * be shown on install.
1287 *
1288 * @param $installer DatabaseInstaller so we can make callbacks
1289 * @return array
1290 */
1291 protected function getInstallSteps( DatabaseInstaller $installer ) {
1292 $coreInstallSteps = array(
1293 array( 'name' => 'database', 'callback' => array( $installer, 'setupDatabase' ) ),
1294 array( 'name' => 'tables', 'callback' => array( $installer, 'createTables' ) ),
1295 array( 'name' => 'interwiki', 'callback' => array( $installer, 'populateInterwikiTable' ) ),
1296 array( 'name' => 'stats', 'callback' => array( $this, 'populateSiteStats' ) ),
1297 array( 'name' => 'keys', 'callback' => array( $this, 'generateKeys' ) ),
1298 array( 'name' => 'sysop', 'callback' => array( $this, 'createSysop' ) ),
1299 array( 'name' => 'mainpage', 'callback' => array( $this, 'createMainpage' ) ),
1300 );
1301
1302 // Build the array of install steps starting from the core install list,
1303 // then adding any callbacks that wanted to attach after a given step
1304 foreach( $coreInstallSteps as $step ) {
1305 $this->installSteps[] = $step;
1306 if( isset( $this->extraInstallSteps[ $step['name'] ] ) ) {
1307 $this->installSteps = array_merge(
1308 $this->installSteps,
1309 $this->extraInstallSteps[ $step['name'] ]
1310 );
1311 }
1312 }
1313
1314 // Prepend any steps that want to be at the beginning
1315 if( isset( $this->extraInstallSteps['BEGINNING'] ) ) {
1316 $this->installSteps = array_merge(
1317 $this->extraInstallSteps['BEGINNING'],
1318 $this->installSteps
1319 );
1320 }
1321
1322 // Extensions should always go first, chance to tie into hooks and such
1323 if( count( $this->getVar( '_Extensions' ) ) ) {
1324 array_unshift( $this->installSteps,
1325 array( 'name' => 'extensions', 'callback' => array( $this, 'includeExtensions' ) )
1326 );
1327 $this->installSteps[] = array(
1328 'name' => 'extension-tables',
1329 'callback' => array( $installer, 'createExtensionTables' )
1330 );
1331 }
1332 return $this->installSteps;
1333 }
1334
1335 /**
1336 * Actually perform the installation.
1337 *
1338 * @param $startCB Array A callback array for the beginning of each step
1339 * @param $endCB Array A callback array for the end of each step
1340 *
1341 * @return Array of Status objects
1342 */
1343 public function performInstallation( $startCB, $endCB ) {
1344 $installResults = array();
1345 $installer = $this->getDBInstaller();
1346 $installer->preInstall();
1347 $steps = $this->getInstallSteps( $installer );
1348 foreach( $steps as $stepObj ) {
1349 $name = $stepObj['name'];
1350 call_user_func_array( $startCB, array( $name ) );
1351
1352 // Perform the callback step
1353 $status = call_user_func( $stepObj['callback'], $installer );
1354
1355 // Output and save the results
1356 call_user_func( $endCB, $name, $status );
1357 $installResults[$name] = $status;
1358
1359 // If we've hit some sort of fatal, we need to bail.
1360 // Callback already had a chance to do output above.
1361 if( !$status->isOk() ) {
1362 break;
1363 }
1364 }
1365 if( $status->isOk() ) {
1366 $this->setVar( '_InstallDone', true );
1367 }
1368 return $installResults;
1369 }
1370
1371 /**
1372 * Generate $wgSecretKey. Will warn if we had to use mt_rand() instead of
1373 * /dev/urandom
1374 *
1375 * @return Status
1376 */
1377 public function generateKeys() {
1378 $keys = array( 'wgSecretKey' => 64 );
1379 if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) {
1380 $keys['wgUpgradeKey'] = 16;
1381 }
1382 return $this->doGenerateKeys( $keys );
1383 }
1384
1385 /**
1386 * Generate a secret value for variables using either
1387 * /dev/urandom or mt_rand(). Produce a warning in the later case.
1388 *
1389 * @param $keys Array
1390 * @return Status
1391 */
1392 protected function doGenerateKeys( $keys ) {
1393 $status = Status::newGood();
1394
1395 wfSuppressWarnings();
1396 $file = fopen( "/dev/urandom", "r" );
1397 wfRestoreWarnings();
1398
1399 foreach ( $keys as $name => $length ) {
1400 if ( $file ) {
1401 $secretKey = bin2hex( fread( $file, $length / 2 ) );
1402 } else {
1403 $secretKey = '';
1404
1405 for ( $i = 0; $i < $length / 8; $i++ ) {
1406 $secretKey .= dechex( mt_rand( 0, 0x7fffffff ) );
1407 }
1408 }
1409
1410 $this->setVar( $name, $secretKey );
1411 }
1412
1413 if ( $file ) {
1414 fclose( $file );
1415 } else {
1416 $names = array_keys ( $keys );
1417 $names = preg_replace( '/^(.*)$/', '\$$1', $names );
1418 global $wgLang;
1419 $status->warning( 'config-insecure-keys', $wgLang->listToText( $names ), count( $names ) );
1420 }
1421
1422 return $status;
1423 }
1424
1425 /**
1426 * Create the first user account, grant it sysop and bureaucrat rights
1427 *
1428 * @return Status
1429 */
1430 protected function createSysop() {
1431 $name = $this->getVar( '_AdminName' );
1432 $user = User::newFromName( $name );
1433
1434 if ( !$user ) {
1435 // We should've validated this earlier anyway!
1436 return Status::newFatal( 'config-admin-error-user', $name );
1437 }
1438
1439 if ( $user->idForName() == 0 ) {
1440 $user->addToDatabase();
1441
1442 try {
1443 $user->setPassword( $this->getVar( '_AdminPassword' ) );
1444 } catch( PasswordError $pwe ) {
1445 return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() );
1446 }
1447
1448 $user->addGroup( 'sysop' );
1449 $user->addGroup( 'bureaucrat' );
1450 if( $this->getVar( '_AdminEmail' ) ) {
1451 $user->setEmail( $this->getVar( '_AdminEmail' ) );
1452 }
1453 $user->saveSettings();
1454
1455 // Update user count
1456 $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 );
1457 $ssUpdate->doUpdate();
1458 }
1459 $status = Status::newGood();
1460
1461 if( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) {
1462 $this->subscribeToMediaWikiAnnounce( $status );
1463 }
1464
1465 return $status;
1466 }
1467
1468 private function subscribeToMediaWikiAnnounce( Status $s ) {
1469 $params = array(
1470 'email' => $this->getVar( '_AdminEmail' ),
1471 'language' => 'en',
1472 'digest' => 0
1473 );
1474
1475 // Mailman doesn't support as many languages as we do, so check to make
1476 // sure their selected language is available
1477 $myLang = $this->getVar( '_UserLang' );
1478 if( in_array( $myLang, $this->mediaWikiAnnounceLanguages ) ) {
1479 $myLang = $myLang == 'pt-br' ? 'pt_BR' : $myLang; // rewrite to Mailman's pt_BR
1480 $params['language'] = $myLang;
1481 }
1482
1483 if( MWHttpRequest::canMakeRequests() ) {
1484 $res = MWHttpRequest::factory( $this->mediaWikiAnnounceUrl,
1485 array( 'method' => 'POST', 'postData' => $params ) )->execute();
1486 if( !$res->isOK() ) {
1487 $s->warning( 'config-install-subscribe-fail', $res->getMessage() );
1488 }
1489 } else {
1490 $s->warning( 'config-install-subscribe-notpossible' );
1491 }
1492 }
1493
1494 /**
1495 * Insert Main Page with default content.
1496 *
1497 * @return Status
1498 */
1499 protected function createMainpage( DatabaseInstaller $installer ) {
1500 $status = Status::newGood();
1501 try {
1502 $article = new Article( Title::newMainPage() );
1503 $article->doEdit( wfMsgForContent( 'mainpagetext' ) . "\n\n" .
1504 wfMsgForContent( 'mainpagedocfooter' ),
1505 '',
1506 EDIT_NEW,
1507 false,
1508 User::newFromName( 'MediaWiki default' ) );
1509 } catch (MWException $e) {
1510 //using raw, because $wgShowExceptionDetails can not be set yet
1511 $status->fatal( 'config-install-mainpage-failed', $e->getMessage() );
1512 }
1513
1514 return $status;
1515 }
1516
1517 /**
1518 * Override the necessary bits of the config to run an installation.
1519 */
1520 public static function overrideConfig() {
1521 define( 'MW_NO_SESSION', 1 );
1522
1523 // Don't access the database
1524 $GLOBALS['wgUseDatabaseMessages'] = false;
1525 // Debug-friendly
1526 $GLOBALS['wgShowExceptionDetails'] = true;
1527 // Don't break forms
1528 $GLOBALS['wgExternalLinkTarget'] = '_blank';
1529
1530 // Extended debugging
1531 $GLOBALS['wgShowSQLErrors'] = true;
1532 $GLOBALS['wgShowDBErrorBacktrace'] = true;
1533
1534 // Allow multiple ob_flush() calls
1535 $GLOBALS['wgDisableOutputCompression'] = true;
1536
1537 // Use a sensible cookie prefix (not my_wiki)
1538 $GLOBALS['wgCookiePrefix'] = 'mw_installer';
1539
1540 // Some of the environment checks make shell requests, remove limits
1541 $GLOBALS['wgMaxShellMemory'] = 0;
1542 }
1543
1544 /**
1545 * Add an installation step following the given step.
1546 *
1547 * @param $callback Array A valid installation callback array, in this form:
1548 * array( 'name' => 'some-unique-name', 'callback' => array( $obj, 'function' ) );
1549 * @param $findStep String the step to find. Omit to put the step at the beginning
1550 */
1551 public function addInstallStep( $callback, $findStep = 'BEGINNING' ) {
1552 $this->extraInstallSteps[$findStep][] = $callback;
1553 }
1554
1555 /**
1556 * Disable the time limit for execution.
1557 * Some long-running pages (Install, Upgrade) will want to do this
1558 */
1559 protected function disableTimeLimit() {
1560 wfSuppressWarnings();
1561 set_time_limit( 0 );
1562 wfRestoreWarnings();
1563 }
1564 }