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