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