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