Merge "Fix PHP CodeSniffer warnings and errors"
[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 'wgServer',
162 'wgScriptPath',
163 'wgScriptExtension',
164 'wgMetaNamespace',
165 'wgDeletedDirectory',
166 'wgEnableUploads',
167 'wgLogo',
168 'wgShellLocale',
169 'wgSecretKey',
170 'wgUseInstantCommons',
171 'wgUpgradeKey',
172 'wgDefaultSkin',
173 'wgResourceLoaderMaxQueryLength',
174 );
175
176 /**
177 * Variables that are stored alongside globals, and are used for any
178 * configuration of the installation process aside from the MediaWiki
179 * configuration. Map of names to defaults.
180 *
181 * @var array
182 */
183 protected $internalDefaults = array(
184 '_UserLang' => 'en',
185 '_Environment' => false,
186 '_SafeMode' => false,
187 '_RaiseMemory' => false,
188 '_UpgradeDone' => false,
189 '_InstallDone' => false,
190 '_Caches' => array(),
191 '_InstallPassword' => '',
192 '_SameAccount' => true,
193 '_CreateDBAccount' => false,
194 '_NamespaceType' => 'site-name',
195 '_AdminName' => '', // will be set later, when the user selects language
196 '_AdminPassword' => '',
197 '_AdminPassword2' => '',
198 '_AdminEmail' => '',
199 '_Subscribe' => false,
200 '_SkipOptional' => 'continue',
201 '_RightsProfile' => 'wiki',
202 '_LicenseCode' => 'none',
203 '_CCDone' => false,
204 '_Extensions' => array(),
205 '_MemCachedServers' => '',
206 '_UpgradeKeySupplied' => false,
207 '_ExistingDBSettings' => false,
208 );
209
210 /**
211 * The actual list of installation steps. This will be initialized by getInstallSteps()
212 *
213 * @var array
214 */
215 private $installSteps = array();
216
217 /**
218 * Extra steps for installation, for things like DatabaseInstallers to modify
219 *
220 * @var array
221 */
222 protected $extraInstallSteps = array();
223
224 /**
225 * Known object cache types and the functions used to test for their existence.
226 *
227 * @var array
228 */
229 protected $objectCaches = array(
230 'xcache' => 'xcache_get',
231 'apc' => 'apc_fetch',
232 'wincache' => 'wincache_ucache_get'
233 );
234
235 /**
236 * User rights profiles.
237 *
238 * @var array
239 */
240 public $rightsProfiles = array(
241 'wiki' => array(),
242 'no-anon' => array(
243 '*' => array( 'edit' => false )
244 ),
245 'fishbowl' => array(
246 '*' => array(
247 'createaccount' => false,
248 'edit' => false,
249 ),
250 ),
251 'private' => array(
252 '*' => array(
253 'createaccount' => false,
254 'edit' => false,
255 'read' => false,
256 ),
257 ),
258 );
259
260 /**
261 * License types.
262 *
263 * @var array
264 */
265 public $licenses = array(
266 'cc-by' => array(
267 'url' => 'http://creativecommons.org/licenses/by/3.0/',
268 'icon' => '{$wgStylePath}/common/images/cc-by.png',
269 ),
270 'cc-by-sa' => array(
271 'url' => 'http://creativecommons.org/licenses/by-sa/3.0/',
272 'icon' => '{$wgStylePath}/common/images/cc-by-sa.png',
273 ),
274 'cc-by-nc-sa' => array(
275 'url' => 'http://creativecommons.org/licenses/by-nc-sa/3.0/',
276 'icon' => '{$wgStylePath}/common/images/cc-by-nc-sa.png',
277 ),
278 'cc-0' => array(
279 'url' => 'https://creativecommons.org/publicdomain/zero/1.0/',
280 'icon' => '{$wgStylePath}/common/images/cc-0.png',
281 ),
282 'pd' => array(
283 'url' => '',
284 'icon' => '{$wgStylePath}/common/images/public-domain.png',
285 ),
286 'gfdl' => array(
287 'url' => 'http://www.gnu.org/copyleft/fdl.html',
288 'icon' => '{$wgStylePath}/common/images/gnu-fdl.png',
289 ),
290 'none' => array(
291 'url' => '',
292 'icon' => '',
293 'text' => ''
294 ),
295 'cc-choose' => array(
296 // Details will be filled in by the selector.
297 'url' => '',
298 'icon' => '',
299 'text' => '',
300 ),
301 );
302
303 /**
304 * URL to mediawiki-announce subscription
305 */
306 protected $mediaWikiAnnounceUrl = 'https://lists.wikimedia.org/mailman/subscribe/mediawiki-announce';
307
308 /**
309 * Supported language codes for Mailman
310 */
311 protected $mediaWikiAnnounceLanguages = array(
312 'ca', 'cs', 'da', 'de', 'en', 'es', 'et', 'eu', 'fi', 'fr', 'hr', 'hu',
313 'it', 'ja', 'ko', 'lt', 'nl', 'no', 'pl', 'pt', 'pt-br', 'ro', 'ru',
314 'sl', 'sr', 'sv', 'tr', 'uk'
315 );
316
317 /**
318 * UI interface for displaying a short message
319 * The parameters are like parameters to wfMessage().
320 * The messages will be in wikitext format, which will be converted to an
321 * output format such as HTML or text before being sent to the user.
322 * @param $msg
323 */
324 abstract public function showMessage( $msg /*, ... */ );
325
326 /**
327 * Same as showMessage(), but for displaying errors
328 * @param $msg
329 */
330 abstract public function showError( $msg /*, ... */ );
331
332 /**
333 * Show a message to the installing user by using a Status object
334 * @param $status Status
335 */
336 abstract public function showStatusMessage( Status $status );
337
338 /**
339 * Constructor, always call this from child classes.
340 */
341 public function __construct() {
342 global $wgExtensionMessagesFiles, $wgUser;
343
344 // Disable the i18n cache and LoadBalancer
345 Language::getLocalisationCache()->disableBackend();
346 LBFactory::disableBackend();
347
348 // Load the installer's i18n file.
349 $wgExtensionMessagesFiles['MediawikiInstaller'] =
350 __DIR__ . '/Installer.i18n.php';
351
352 // Having a user with id = 0 safeguards us from DB access via User::loadOptions().
353 $wgUser = User::newFromId( 0 );
354
355 $this->settings = $this->internalDefaults;
356
357 foreach ( $this->defaultVarNames as $var ) {
358 $this->settings[$var] = $GLOBALS[$var];
359 }
360
361 $compiledDBs = array();
362 foreach ( self::getDBTypes() as $type ) {
363 $installer = $this->getDBInstaller( $type );
364
365 if ( !$installer->isCompiled() ) {
366 continue;
367 }
368 $compiledDBs[] = $type;
369
370 $defaults = $installer->getGlobalDefaults();
371
372 foreach ( $installer->getGlobalNames() as $var ) {
373 if ( isset( $defaults[$var] ) ) {
374 $this->settings[$var] = $defaults[$var];
375 } else {
376 $this->settings[$var] = $GLOBALS[$var];
377 }
378 }
379 }
380 $this->compiledDBs = $compiledDBs;
381
382 $this->parserTitle = Title::newFromText( 'Installer' );
383 $this->parserOptions = new ParserOptions; // language will be wrong :(
384 $this->parserOptions->setEditSection( false );
385 }
386
387 /**
388 * Get a list of known DB types.
389 *
390 * @return array
391 */
392 public static function getDBTypes() {
393 return self::$dbTypes;
394 }
395
396 /**
397 * Do initial checks of the PHP environment. Set variables according to
398 * the observed environment.
399 *
400 * It's possible that this may be called under the CLI SAPI, not the SAPI
401 * that the wiki will primarily run under. In that case, the subclass should
402 * initialise variables such as wgScriptPath, before calling this function.
403 *
404 * Under the web subclass, it can already be assumed that PHP 5+ is in use
405 * and that sessions are working.
406 *
407 * @return Status
408 */
409 public function doEnvironmentChecks() {
410 $phpVersion = phpversion();
411 if ( version_compare( $phpVersion, self::MINIMUM_PHP_VERSION, '>=' ) ) {
412 $this->showMessage( 'config-env-php', $phpVersion );
413 $good = true;
414 } else {
415 $this->showMessage( 'config-env-php-toolow', $phpVersion, self::MINIMUM_PHP_VERSION );
416 $good = false;
417 }
418
419 if ( $good ) {
420 foreach ( $this->envChecks as $check ) {
421 $status = $this->$check();
422 if ( $status === false ) {
423 $good = false;
424 }
425 }
426 }
427
428 $this->setVar( '_Environment', $good );
429
430 return $good ? Status::newGood() : Status::newFatal( 'config-env-bad' );
431 }
432
433 /**
434 * Set a MW configuration variable, or internal installer configuration variable.
435 *
436 * @param $name String
437 * @param $value Mixed
438 */
439 public function setVar( $name, $value ) {
440 $this->settings[$name] = $value;
441 }
442
443 /**
444 * Get an MW configuration variable, or internal installer configuration variable.
445 * The defaults come from $GLOBALS (ultimately DefaultSettings.php).
446 * Installer variables are typically prefixed by an underscore.
447 *
448 * @param $name String
449 * @param $default Mixed
450 *
451 * @return mixed
452 */
453 public function getVar( $name, $default = null ) {
454 if ( !isset( $this->settings[$name] ) ) {
455 return $default;
456 } else {
457 return $this->settings[$name];
458 }
459 }
460
461 /**
462 * Get a list of DBs supported by current PHP setup
463 *
464 * @return array
465 */
466 public function getCompiledDBs() {
467 return $this->compiledDBs;
468 }
469
470 /**
471 * Get an instance of DatabaseInstaller for the specified DB type.
472 *
473 * @param $type Mixed: DB installer for which is needed, false to use default.
474 *
475 * @return DatabaseInstaller
476 */
477 public function getDBInstaller( $type = false ) {
478 if ( !$type ) {
479 $type = $this->getVar( 'wgDBtype' );
480 }
481
482 $type = strtolower( $type );
483
484 if ( !isset( $this->dbInstallers[$type] ) ) {
485 $class = ucfirst( $type ) . 'Installer';
486 $this->dbInstallers[$type] = new $class( $this );
487 }
488
489 return $this->dbInstallers[$type];
490 }
491
492 /**
493 * Determine if LocalSettings.php exists. If it does, return its variables,
494 * merged with those from AdminSettings.php, as an array.
495 *
496 * @return Array
497 */
498 public static function getExistingLocalSettings() {
499 global $IP;
500
501 wfSuppressWarnings();
502 $_lsExists = file_exists( "$IP/LocalSettings.php" );
503 wfRestoreWarnings();
504
505 if ( !$_lsExists ) {
506 return false;
507 }
508 unset( $_lsExists );
509
510 require "$IP/includes/DefaultSettings.php";
511 require "$IP/LocalSettings.php";
512 if ( file_exists( "$IP/AdminSettings.php" ) ) {
513 require "$IP/AdminSettings.php";
514 }
515
516 return get_defined_vars();
517 }
518
519 /**
520 * Get a fake password for sending back to the user in HTML.
521 * This is a security mechanism to avoid compromise of the password in the
522 * event of session ID compromise.
523 *
524 * @param $realPassword String
525 *
526 * @return string
527 */
528 public function getFakePassword( $realPassword ) {
529 return str_repeat( '*', strlen( $realPassword ) );
530 }
531
532 /**
533 * Set a variable which stores a password, except if the new value is a
534 * fake password in which case leave it as it is.
535 *
536 * @param $name String
537 * @param $value Mixed
538 */
539 public function setPassword( $name, $value ) {
540 if ( !preg_match( '/^\*+$/', $value ) ) {
541 $this->setVar( $name, $value );
542 }
543 }
544
545 /**
546 * On POSIX systems return the primary group of the webserver we're running under.
547 * On other systems just returns null.
548 *
549 * This is used to advice the user that he should chgrp his mw-config/data/images directory as the
550 * webserver user before he can install.
551 *
552 * Public because SqliteInstaller needs it, and doesn't subclass Installer.
553 *
554 * @return mixed
555 */
556 public static function maybeGetWebserverPrimaryGroup() {
557 if ( !function_exists( 'posix_getegid' ) || !function_exists( 'posix_getpwuid' ) ) {
558 # I don't know this, this isn't UNIX.
559 return null;
560 }
561
562 # posix_getegid() *not* getmygid() because we want the group of the webserver,
563 # not whoever owns the current script.
564 $gid = posix_getegid();
565 $getpwuid = posix_getpwuid( $gid );
566 $group = $getpwuid['name'];
567
568 return $group;
569 }
570
571 /**
572 * Convert wikitext $text to HTML.
573 *
574 * This is potentially error prone since many parser features require a complete
575 * installed MW database. The solution is to just not use those features when you
576 * write your messages. This appears to work well enough. Basic formatting and
577 * external links work just fine.
578 *
579 * But in case a translator decides to throw in a "#ifexist" or internal link or
580 * whatever, this function is guarded to catch the attempted DB access and to present
581 * some fallback text.
582 *
583 * @param $text String
584 * @param $lineStart Boolean
585 * @return String
586 */
587 public function parse( $text, $lineStart = false ) {
588 global $wgParser;
589
590 try {
591 $out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
592 $html = $out->getText();
593 } catch ( DBAccessError $e ) {
594 $html = '<!--DB access attempted during parse--> ' . htmlspecialchars( $text );
595
596 if ( !empty( $this->debug ) ) {
597 $html .= "<!--\n" . $e->getTraceAsString() . "\n-->";
598 }
599 }
600
601 return $html;
602 }
603
604 /**
605 * @return ParserOptions
606 */
607 public function getParserOptions() {
608 return $this->parserOptions;
609 }
610
611 public function disableLinkPopups() {
612 $this->parserOptions->setExternalLinkTarget( false );
613 }
614
615 public function restoreLinkPopups() {
616 global $wgExternalLinkTarget;
617 $this->parserOptions->setExternalLinkTarget( $wgExternalLinkTarget );
618 }
619
620 /**
621 * Install step which adds a row to the site_stats table with appropriate
622 * initial values.
623 *
624 * @param $installer DatabaseInstaller
625 *
626 * @return Status
627 */
628 public function populateSiteStats( DatabaseInstaller $installer ) {
629 $status = $installer->getConnection();
630 if ( !$status->isOK() ) {
631 return $status;
632 }
633 $status->value->insert( 'site_stats', array(
634 'ss_row_id' => 1,
635 'ss_total_views' => 0,
636 'ss_total_edits' => 0,
637 'ss_good_articles' => 0,
638 'ss_total_pages' => 0,
639 'ss_users' => 0,
640 'ss_images' => 0 ),
641 __METHOD__, 'IGNORE' );
642
643 return Status::newGood();
644 }
645
646 /**
647 * Exports all wg* variables stored by the installer into global scope.
648 */
649 public function exportVars() {
650 foreach ( $this->settings as $name => $value ) {
651 if ( substr( $name, 0, 2 ) == 'wg' ) {
652 $GLOBALS[$name] = $value;
653 }
654 }
655 }
656
657 /**
658 * Environment check for DB types.
659 * @return bool
660 */
661 protected function envCheckDB() {
662 global $wgLang;
663
664 $allNames = array();
665
666 // Messages: config-type-mysql, config-type-postgres, config-type-oracle,
667 // config-type-sqlite
668 foreach ( self::getDBTypes() as $name ) {
669 $allNames[] = wfMessage( "config-type-$name" )->text();
670 }
671
672 $databases = $this->getCompiledDBs();
673
674 $databases = array_flip( $databases );
675 foreach ( array_keys( $databases ) as $db ) {
676 $installer = $this->getDBInstaller( $db );
677 $status = $installer->checkPrerequisites();
678 if ( !$status->isGood() ) {
679 $this->showStatusMessage( $status );
680 }
681 if ( !$status->isOK() ) {
682 unset( $databases[$db] );
683 }
684 }
685 $databases = array_flip( $databases );
686 if ( !$databases ) {
687 $this->showError( 'config-no-db', $wgLang->commaList( $allNames ) );
688
689 // @todo FIXME: This only works for the web installer!
690 return false;
691 }
692
693 return true;
694 }
695
696 /**
697 * Environment check for register_globals.
698 */
699 protected function envCheckRegisterGlobals() {
700 if ( wfIniGetBool( 'register_globals' ) ) {
701 $this->showMessage( 'config-register-globals' );
702 }
703 }
704
705 /**
706 * Some versions of libxml+PHP break < and > encoding horribly
707 * @return bool
708 */
709 protected function envCheckBrokenXML() {
710 $test = new PhpXmlBugTester();
711 if ( !$test->ok ) {
712 $this->showError( 'config-brokenlibxml' );
713
714 return false;
715 }
716
717 return true;
718 }
719
720 /**
721 * Test PHP (probably 5.3.1, but it could regress again) to make sure that
722 * reference parameters to __call() are not converted to null
723 * @return bool
724 */
725 protected function envCheckPHP531() {
726 $test = new PhpRefCallBugTester;
727 $test->execute();
728 if ( !$test->ok ) {
729 $this->showError( 'config-using531', phpversion() );
730
731 return false;
732 }
733
734 return true;
735 }
736
737 /**
738 * Environment check for magic_quotes_runtime.
739 * @return bool
740 */
741 protected function envCheckMagicQuotes() {
742 if ( wfIniGetBool( "magic_quotes_runtime" ) ) {
743 $this->showError( 'config-magic-quotes-runtime' );
744
745 return false;
746 }
747
748 return true;
749 }
750
751 /**
752 * Environment check for magic_quotes_sybase.
753 * @return bool
754 */
755 protected function envCheckMagicSybase() {
756 if ( wfIniGetBool( 'magic_quotes_sybase' ) ) {
757 $this->showError( 'config-magic-quotes-sybase' );
758
759 return false;
760 }
761
762 return true;
763 }
764
765 /**
766 * Environment check for mbstring.func_overload.
767 * @return bool
768 */
769 protected function envCheckMbstring() {
770 if ( wfIniGetBool( 'mbstring.func_overload' ) ) {
771 $this->showError( 'config-mbstring' );
772
773 return false;
774 }
775
776 return true;
777 }
778
779 /**
780 * Environment check for zend.ze1_compatibility_mode.
781 * @return bool
782 */
783 protected function envCheckZE1() {
784 if ( wfIniGetBool( 'zend.ze1_compatibility_mode' ) ) {
785 $this->showError( 'config-ze1' );
786
787 return false;
788 }
789
790 return true;
791 }
792
793 /**
794 * Environment check for safe_mode.
795 * @return bool
796 */
797 protected function envCheckSafeMode() {
798 if ( wfIniGetBool( 'safe_mode' ) ) {
799 $this->setVar( '_SafeMode', true );
800 $this->showMessage( 'config-safe-mode' );
801 }
802
803 return true;
804 }
805
806 /**
807 * Environment check for the XML module.
808 * @return bool
809 */
810 protected function envCheckXML() {
811 if ( !function_exists( "utf8_encode" ) ) {
812 $this->showError( 'config-xml-bad' );
813
814 return false;
815 }
816
817 return true;
818 }
819
820 /**
821 * Environment check for the PCRE module.
822 *
823 * @note If this check were to fail, the parser would
824 * probably throw an exception before the result
825 * of this check is shown to the user.
826 * @return bool
827 */
828 protected function envCheckPCRE() {
829 if ( !function_exists( 'preg_match' ) ) {
830 $this->showError( 'config-pcre' );
831
832 return false;
833 }
834 wfSuppressWarnings();
835 $regexd = preg_replace( '/[\x{0430}-\x{04FF}]/iu', '', '-АБВГД-' );
836 // Need to check for \p support too, as PCRE can be compiled
837 // with utf8 support, but not unicode property support.
838 // check that \p{Zs} (space separators) matches
839 // U+3000 (Ideographic space)
840 $regexprop = preg_replace( '/\p{Zs}/u', '', "-\xE3\x80\x80-" );
841 wfRestoreWarnings();
842 if ( $regexd != '--' || $regexprop != '--' ) {
843 $this->showError( 'config-pcre-no-utf8' );
844
845 return false;
846 }
847
848 return true;
849 }
850
851 /**
852 * Environment check for available memory.
853 * @return bool
854 */
855 protected function envCheckMemory() {
856 $limit = ini_get( 'memory_limit' );
857
858 if ( !$limit || $limit == -1 ) {
859 return true;
860 }
861
862 $n = wfShorthandToInteger( $limit );
863
864 if ( $n < $this->minMemorySize * 1024 * 1024 ) {
865 $newLimit = "{$this->minMemorySize}M";
866
867 if ( ini_set( "memory_limit", $newLimit ) === false ) {
868 $this->showMessage( 'config-memory-bad', $limit );
869 } else {
870 $this->showMessage( 'config-memory-raised', $limit, $newLimit );
871 $this->setVar( '_RaiseMemory', true );
872 }
873 }
874
875 return true;
876 }
877
878 /**
879 * Environment check for compiled object cache types.
880 */
881 protected function envCheckCache() {
882 $caches = array();
883 foreach ( $this->objectCaches as $name => $function ) {
884 if ( function_exists( $function ) ) {
885 if ( $name == 'xcache' && !wfIniGetBool( 'xcache.var_size' ) ) {
886 continue;
887 }
888 $caches[$name] = true;
889 }
890 }
891
892 if ( !$caches ) {
893 $this->showMessage( 'config-no-cache' );
894 }
895
896 $this->setVar( '_Caches', $caches );
897 }
898
899 /**
900 * Scare user to death if they have mod_security
901 * @return bool
902 */
903 protected function envCheckModSecurity() {
904 if ( self::apacheModulePresent( 'mod_security' ) ) {
905 $this->showMessage( 'config-mod-security' );
906 }
907
908 return true;
909 }
910
911 /**
912 * Search for GNU diff3.
913 * @return bool
914 */
915 protected function envCheckDiff3() {
916 $names = array( "gdiff3", "diff3", "diff3.exe" );
917 $versionInfo = array( '$1 --version 2>&1', 'GNU diffutils' );
918
919 $diff3 = self::locateExecutableInDefaultPaths( $names, $versionInfo );
920
921 if ( $diff3 ) {
922 $this->setVar( 'wgDiff3', $diff3 );
923 } else {
924 $this->setVar( 'wgDiff3', false );
925 $this->showMessage( 'config-diff3-bad' );
926 }
927
928 return true;
929 }
930
931 /**
932 * Environment check for ImageMagick and GD.
933 * @return bool
934 */
935 protected function envCheckGraphics() {
936 $names = array( wfIsWindows() ? 'convert.exe' : 'convert' );
937 $versionInfo = array( '$1 -version', 'ImageMagick' );
938 $convert = self::locateExecutableInDefaultPaths( $names, $versionInfo );
939
940 $this->setVar( 'wgImageMagickConvertCommand', '' );
941 if ( $convert ) {
942 $this->setVar( 'wgImageMagickConvertCommand', $convert );
943 $this->showMessage( 'config-imagemagick', $convert );
944
945 return true;
946 } elseif ( function_exists( 'imagejpeg' ) ) {
947 $this->showMessage( 'config-gd' );
948 } else {
949 $this->showMessage( 'config-no-scaling' );
950 }
951
952 return true;
953 }
954
955 /**
956 * Search for git.
957 *
958 * @since 1.22
959 * @return bool
960 */
961 protected function envCheckGit() {
962 $names = array( wfIsWindows() ? 'git.exe' : 'git' );
963 $versionInfo = array( '$1 --version', 'git version' );
964
965 $git = self::locateExecutableInDefaultPaths( $names, $versionInfo );
966
967 if ( $git ) {
968 $this->setVar( 'wgGitBin', $git );
969 $this->showMessage( 'config-git', $git );
970 } else {
971 $this->setVar( 'wgGitBin', false );
972 $this->showMessage( 'config-git-bad' );
973 }
974
975 return true;
976 }
977
978 /**
979 * Environment check for the server hostname.
980 */
981 protected function envCheckServer() {
982 $server = $this->envGetDefaultServer();
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 }