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