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