Merge "Move white-space: nowrap from PHP to CSS"
[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 * The oldest version of PCRE we can support.
46 *
47 * Defining this is necessary because PHP may be linked with a system version
48 * of PCRE, which may be older than that bundled with the minimum PHP version.
49 */
50 const MINIMUM_PCRE_VERSION = '7.2';
51
52 /**
53 * @var array
54 */
55 protected $settings;
56
57 /**
58 * List of detected DBs, access using getCompiledDBs().
59 *
60 * @var array
61 */
62 protected $compiledDBs;
63
64 /**
65 * Cached DB installer instances, access using getDBInstaller().
66 *
67 * @var array
68 */
69 protected $dbInstallers = array();
70
71 /**
72 * Minimum memory size in MB.
73 *
74 * @var int
75 */
76 protected $minMemorySize = 50;
77
78 /**
79 * Cached Title, used by parse().
80 *
81 * @var Title
82 */
83 protected $parserTitle;
84
85 /**
86 * Cached ParserOptions, used by parse().
87 *
88 * @var ParserOptions
89 */
90 protected $parserOptions;
91
92 /**
93 * Known database types. These correspond to the class names <type>Installer,
94 * and are also MediaWiki database types valid for $wgDBtype.
95 *
96 * To add a new type, create a <type>Installer class and a Database<type>
97 * class, and add a config-type-<type> message to MessagesEn.php.
98 *
99 * @var array
100 */
101 protected static $dbTypes = array(
102 'mysql',
103 'postgres',
104 'oracle',
105 'mssql',
106 'sqlite',
107 );
108
109 /**
110 * A list of environment check methods called by doEnvironmentChecks().
111 * These may output warnings using showMessage(), and/or abort the
112 * installation process by returning false.
113 *
114 * @var array
115 */
116 protected $envChecks = array(
117 'envCheckDB',
118 'envCheckRegisterGlobals',
119 'envCheckBrokenXML',
120 'envCheckMagicQuotes',
121 'envCheckMagicSybase',
122 'envCheckMbstring',
123 'envCheckSafeMode',
124 'envCheckXML',
125 'envCheckPCRE',
126 'envCheckMemory',
127 'envCheckCache',
128 'envCheckModSecurity',
129 'envCheckDiff3',
130 'envCheckGraphics',
131 'envCheckGit',
132 'envCheckServer',
133 'envCheckPath',
134 'envCheckExtension',
135 'envCheckShellLocale',
136 'envCheckUploadsDirectory',
137 'envCheckLibicu',
138 'envCheckSuhosinMaxValueLength',
139 'envCheckCtype',
140 'envCheckJSON',
141 );
142
143 /**
144 * MediaWiki configuration globals that will eventually be passed through
145 * to LocalSettings.php. The names only are given here, the defaults
146 * typically come from DefaultSettings.php.
147 *
148 * @var array
149 */
150 protected $defaultVarNames = array(
151 'wgSitename',
152 'wgPasswordSender',
153 'wgLanguageCode',
154 'wgRightsIcon',
155 'wgRightsText',
156 'wgRightsUrl',
157 'wgMainCacheType',
158 'wgEnableEmail',
159 'wgEnableUserEmail',
160 'wgEnotifUserTalk',
161 'wgEnotifWatchlist',
162 'wgEmailAuthentication',
163 'wgDBtype',
164 'wgDiff3',
165 'wgImageMagickConvertCommand',
166 'wgGitBin',
167 'IP',
168 'wgScriptPath',
169 'wgScriptExtension',
170 'wgMetaNamespace',
171 'wgDeletedDirectory',
172 'wgEnableUploads',
173 'wgShellLocale',
174 'wgSecretKey',
175 'wgUseInstantCommons',
176 'wgUpgradeKey',
177 'wgDefaultSkin',
178 'wgResourceLoaderMaxQueryLength',
179 );
180
181 /**
182 * Variables that are stored alongside globals, and are used for any
183 * configuration of the installation process aside from the MediaWiki
184 * configuration. Map of names to defaults.
185 *
186 * @var array
187 */
188 protected $internalDefaults = array(
189 '_UserLang' => 'en',
190 '_Environment' => false,
191 '_SafeMode' => false,
192 '_RaiseMemory' => false,
193 '_UpgradeDone' => false,
194 '_InstallDone' => false,
195 '_Caches' => array(),
196 '_InstallPassword' => '',
197 '_SameAccount' => true,
198 '_CreateDBAccount' => false,
199 '_NamespaceType' => 'site-name',
200 '_AdminName' => '', // will be set later, when the user selects language
201 '_AdminPassword' => '',
202 '_AdminPasswordConfirm' => '',
203 '_AdminEmail' => '',
204 '_Subscribe' => false,
205 '_SkipOptional' => 'continue',
206 '_RightsProfile' => 'wiki',
207 '_LicenseCode' => 'none',
208 '_CCDone' => false,
209 '_Extensions' => array(),
210 '_MemCachedServers' => '',
211 '_UpgradeKeySupplied' => false,
212 '_ExistingDBSettings' => false,
213
214 // $wgLogo is probably wrong (bug 48084); set something that will work.
215 // Single quotes work fine here, as LocalSettingsGenerator outputs this unescaped.
216 'wgLogo' => '$wgStylePath/common/images/wiki.png',
217 );
218
219 /**
220 * The actual list of installation steps. This will be initialized by getInstallSteps()
221 *
222 * @var array
223 */
224 private $installSteps = array();
225
226 /**
227 * Extra steps for installation, for things like DatabaseInstallers to modify
228 *
229 * @var array
230 */
231 protected $extraInstallSteps = array();
232
233 /**
234 * Known object cache types and the functions used to test for their existence.
235 *
236 * @var array
237 */
238 protected $objectCaches = array(
239 'xcache' => 'xcache_get',
240 'apc' => 'apc_fetch',
241 'wincache' => 'wincache_ucache_get'
242 );
243
244 /**
245 * User rights profiles.
246 *
247 * @var array
248 */
249 public $rightsProfiles = array(
250 'wiki' => array(),
251 'no-anon' => array(
252 '*' => array( 'edit' => false )
253 ),
254 'fishbowl' => array(
255 '*' => array(
256 'createaccount' => false,
257 'edit' => false,
258 ),
259 ),
260 'private' => array(
261 '*' => array(
262 'createaccount' => false,
263 'edit' => false,
264 'read' => false,
265 ),
266 ),
267 );
268
269 /**
270 * License types.
271 *
272 * @var array
273 */
274 public $licenses = array(
275 'cc-by' => array(
276 'url' => 'http://creativecommons.org/licenses/by/3.0/',
277 'icon' => '{$wgStylePath}/common/images/cc-by.png',
278 ),
279 'cc-by-sa' => array(
280 'url' => 'http://creativecommons.org/licenses/by-sa/3.0/',
281 'icon' => '{$wgStylePath}/common/images/cc-by-sa.png',
282 ),
283 'cc-by-nc-sa' => array(
284 'url' => 'http://creativecommons.org/licenses/by-nc-sa/3.0/',
285 'icon' => '{$wgStylePath}/common/images/cc-by-nc-sa.png',
286 ),
287 'cc-0' => array(
288 'url' => 'https://creativecommons.org/publicdomain/zero/1.0/',
289 'icon' => '{$wgStylePath}/common/images/cc-0.png',
290 ),
291 'pd' => array(
292 'url' => '',
293 'icon' => '{$wgStylePath}/common/images/public-domain.png',
294 ),
295 'gfdl' => array(
296 'url' => 'http://www.gnu.org/copyleft/fdl.html',
297 'icon' => '{$wgStylePath}/common/images/gnu-fdl.png',
298 ),
299 'none' => array(
300 'url' => '',
301 'icon' => '',
302 'text' => ''
303 ),
304 'cc-choose' => array(
305 // Details will be filled in by the selector.
306 'url' => '',
307 'icon' => '',
308 'text' => '',
309 ),
310 );
311
312 /**
313 * URL to mediawiki-announce subscription
314 */
315 protected $mediaWikiAnnounceUrl =
316 'https://lists.wikimedia.org/mailman/subscribe/mediawiki-announce';
317
318 /**
319 * Supported language codes for Mailman
320 */
321 protected $mediaWikiAnnounceLanguages = array(
322 'ca', 'cs', 'da', 'de', 'en', 'es', 'et', 'eu', 'fi', 'fr', 'hr', 'hu',
323 'it', 'ja', 'ko', 'lt', 'nl', 'no', 'pl', 'pt', 'pt-br', 'ro', 'ru',
324 'sl', 'sr', 'sv', 'tr', 'uk'
325 );
326
327 /**
328 * UI interface for displaying a short message
329 * The parameters are like parameters to wfMessage().
330 * The messages will be in wikitext format, which will be converted to an
331 * output format such as HTML or text before being sent to the user.
332 * @param string $msg
333 */
334 abstract public function showMessage( $msg /*, ... */ );
335
336 /**
337 * Same as showMessage(), but for displaying errors
338 * @param string $msg
339 */
340 abstract public function showError( $msg /*, ... */ );
341
342 /**
343 * Show a message to the installing user by using a Status object
344 * @param Status $status
345 */
346 abstract public function showStatusMessage( Status $status );
347
348 /**
349 * Constructor, always call this from child classes.
350 */
351 public function __construct() {
352 global $wgMessagesDirs, $wgUser;
353
354 // Disable the i18n cache
355 Language::getLocalisationCache()->disableBackend();
356 // Disable LoadBalancer and wfGetDB etc.
357 LBFactory::disableBackend();
358
359 // Disable object cache (otherwise CACHE_ANYTHING will try CACHE_DB and
360 // SqlBagOStuff will then throw since we just disabled wfGetDB)
361 $GLOBALS['wgMemc'] = new EmptyBagOStuff;
362 ObjectCache::clear();
363 $emptyCache = array( 'class' => 'EmptyBagOStuff' );
364 $GLOBALS['wgObjectCaches'] = array(
365 CACHE_NONE => $emptyCache,
366 CACHE_DB => $emptyCache,
367 CACHE_ANYTHING => $emptyCache,
368 CACHE_MEMCACHED => $emptyCache,
369 );
370
371 // Load the installer's i18n.
372 $wgMessagesDirs['MediawikiInstaller'] = __DIR__ . '/i18n';
373
374 // Having a user with id = 0 safeguards us from DB access via User::loadOptions().
375 $wgUser = User::newFromId( 0 );
376
377 $this->settings = $this->internalDefaults;
378
379 foreach ( $this->defaultVarNames as $var ) {
380 $this->settings[$var] = $GLOBALS[$var];
381 }
382
383 $this->compiledDBs = array();
384 foreach ( self::getDBTypes() as $type ) {
385 $installer = $this->getDBInstaller( $type );
386
387 if ( !$installer->isCompiled() ) {
388 continue;
389 }
390 $this->compiledDBs[] = $type;
391 }
392
393 $this->parserTitle = Title::newFromText( 'Installer' );
394 $this->parserOptions = new ParserOptions; // language will be wrong :(
395 $this->parserOptions->setEditSection( false );
396 }
397
398 /**
399 * Get a list of known DB types.
400 *
401 * @return array
402 */
403 public static function getDBTypes() {
404 return self::$dbTypes;
405 }
406
407 /**
408 * Do initial checks of the PHP environment. Set variables according to
409 * the observed environment.
410 *
411 * It's possible that this may be called under the CLI SAPI, not the SAPI
412 * that the wiki will primarily run under. In that case, the subclass should
413 * initialise variables such as wgScriptPath, before calling this function.
414 *
415 * Under the web subclass, it can already be assumed that PHP 5+ is in use
416 * and that sessions are working.
417 *
418 * @return Status
419 */
420 public function doEnvironmentChecks() {
421 $phpVersion = phpversion();
422 if ( version_compare( $phpVersion, self::MINIMUM_PHP_VERSION, '>=' ) ) {
423 $this->showMessage( 'config-env-php', $phpVersion );
424 $good = true;
425 } else {
426 $this->showMessage( 'config-env-php-toolow', $phpVersion, self::MINIMUM_PHP_VERSION );
427 $good = false;
428 }
429
430 // Must go here because an old version of PCRE can prevent other checks from completing
431 if ( $good ) {
432 list( $pcreVersion ) = explode( ' ', PCRE_VERSION, 2 );
433 if ( version_compare( $pcreVersion, self::MINIMUM_PCRE_VERSION, '<' ) ) {
434 $this->showError( 'config-pcre-old', self::MINIMUM_PCRE_VERSION, $pcreVersion );
435 $good = false;
436 }
437 }
438
439 if ( $good ) {
440 foreach ( $this->envChecks as $check ) {
441 $status = $this->$check();
442 if ( $status === false ) {
443 $good = false;
444 }
445 }
446 }
447
448 $this->setVar( '_Environment', $good );
449
450 return $good ? Status::newGood() : Status::newFatal( 'config-env-bad' );
451 }
452
453 /**
454 * Set a MW configuration variable, or internal installer configuration variable.
455 *
456 * @param string $name
457 * @param mixed $value
458 */
459 public function setVar( $name, $value ) {
460 $this->settings[$name] = $value;
461 }
462
463 /**
464 * Get an MW configuration variable, or internal installer configuration variable.
465 * The defaults come from $GLOBALS (ultimately DefaultSettings.php).
466 * Installer variables are typically prefixed by an underscore.
467 *
468 * @param string $name
469 * @param mixed $default
470 *
471 * @return mixed
472 */
473 public function getVar( $name, $default = null ) {
474 if ( !isset( $this->settings[$name] ) ) {
475 return $default;
476 } else {
477 return $this->settings[$name];
478 }
479 }
480
481 /**
482 * Get a list of DBs supported by current PHP setup
483 *
484 * @return array
485 */
486 public function getCompiledDBs() {
487 return $this->compiledDBs;
488 }
489
490 /**
491 * Get an instance of DatabaseInstaller for the specified DB type.
492 *
493 * @param mixed $type DB installer for which is needed, false to use default.
494 *
495 * @return DatabaseInstaller
496 */
497 public function getDBInstaller( $type = false ) {
498 if ( !$type ) {
499 $type = $this->getVar( 'wgDBtype' );
500 }
501
502 $type = strtolower( $type );
503
504 if ( !isset( $this->dbInstallers[$type] ) ) {
505 $class = ucfirst( $type ) . 'Installer';
506 $this->dbInstallers[$type] = new $class( $this );
507 }
508
509 return $this->dbInstallers[$type];
510 }
511
512 /**
513 * Determine if LocalSettings.php exists. If it does, return its variables.
514 *
515 * @return array
516 */
517 public static function getExistingLocalSettings() {
518 global $IP;
519
520 wfSuppressWarnings();
521 $_lsExists = file_exists( "$IP/LocalSettings.php" );
522 wfRestoreWarnings();
523
524 if ( !$_lsExists ) {
525 return false;
526 }
527 unset( $_lsExists );
528
529 require "$IP/includes/DefaultSettings.php";
530 require "$IP/LocalSettings.php";
531
532 return get_defined_vars();
533 }
534
535 /**
536 * Get a fake password for sending back to the user in HTML.
537 * This is a security mechanism to avoid compromise of the password in the
538 * event of session ID compromise.
539 *
540 * @param string $realPassword
541 *
542 * @return string
543 */
544 public function getFakePassword( $realPassword ) {
545 return str_repeat( '*', strlen( $realPassword ) );
546 }
547
548 /**
549 * Set a variable which stores a password, except if the new value is a
550 * fake password in which case leave it as it is.
551 *
552 * @param string $name
553 * @param mixed $value
554 */
555 public function setPassword( $name, $value ) {
556 if ( !preg_match( '/^\*+$/', $value ) ) {
557 $this->setVar( $name, $value );
558 }
559 }
560
561 /**
562 * On POSIX systems return the primary group of the webserver we're running under.
563 * On other systems just returns null.
564 *
565 * This is used to advice the user that he should chgrp his mw-config/data/images directory as the
566 * webserver user before he can install.
567 *
568 * Public because SqliteInstaller needs it, and doesn't subclass Installer.
569 *
570 * @return mixed
571 */
572 public static function maybeGetWebserverPrimaryGroup() {
573 if ( !function_exists( 'posix_getegid' ) || !function_exists( 'posix_getpwuid' ) ) {
574 # I don't know this, this isn't UNIX.
575 return null;
576 }
577
578 # posix_getegid() *not* getmygid() because we want the group of the webserver,
579 # not whoever owns the current script.
580 $gid = posix_getegid();
581 $getpwuid = posix_getpwuid( $gid );
582 $group = $getpwuid['name'];
583
584 return $group;
585 }
586
587 /**
588 * Convert wikitext $text to HTML.
589 *
590 * This is potentially error prone since many parser features require a complete
591 * installed MW database. The solution is to just not use those features when you
592 * write your messages. This appears to work well enough. Basic formatting and
593 * external links work just fine.
594 *
595 * But in case a translator decides to throw in a "#ifexist" or internal link or
596 * whatever, this function is guarded to catch the attempted DB access and to present
597 * some fallback text.
598 *
599 * @param string $text
600 * @param bool $lineStart
601 * @return string
602 */
603 public function parse( $text, $lineStart = false ) {
604 global $wgParser;
605
606 try {
607 $out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
608 $html = $out->getText();
609 } catch ( DBAccessError $e ) {
610 $html = '<!--DB access attempted during parse--> ' . htmlspecialchars( $text );
611
612 if ( !empty( $this->debug ) ) {
613 $html .= "<!--\n" . $e->getTraceAsString() . "\n-->";
614 }
615 }
616
617 return $html;
618 }
619
620 /**
621 * @return ParserOptions
622 */
623 public function getParserOptions() {
624 return $this->parserOptions;
625 }
626
627 public function disableLinkPopups() {
628 $this->parserOptions->setExternalLinkTarget( false );
629 }
630
631 public function restoreLinkPopups() {
632 global $wgExternalLinkTarget;
633 $this->parserOptions->setExternalLinkTarget( $wgExternalLinkTarget );
634 }
635
636 /**
637 * Install step which adds a row to the site_stats table with appropriate
638 * initial values.
639 *
640 * @param DatabaseInstaller $installer
641 *
642 * @return Status
643 */
644 public function populateSiteStats( DatabaseInstaller $installer ) {
645 $status = $installer->getConnection();
646 if ( !$status->isOK() ) {
647 return $status;
648 }
649 $status->value->insert(
650 'site_stats',
651 array(
652 'ss_row_id' => 1,
653 'ss_total_views' => 0,
654 'ss_total_edits' => 0,
655 'ss_good_articles' => 0,
656 'ss_total_pages' => 0,
657 'ss_users' => 0,
658 'ss_images' => 0
659 ),
660 __METHOD__, 'IGNORE'
661 );
662
663 return Status::newGood();
664 }
665
666 /**
667 * Exports all wg* variables stored by the installer into global scope.
668 */
669 public function exportVars() {
670 foreach ( $this->settings as $name => $value ) {
671 if ( substr( $name, 0, 2 ) == 'wg' ) {
672 $GLOBALS[$name] = $value;
673 }
674 }
675 }
676
677 /**
678 * Environment check for DB types.
679 * @return bool
680 */
681 protected function envCheckDB() {
682 global $wgLang;
683
684 $allNames = array();
685
686 // Messages: config-type-mysql, config-type-postgres, config-type-oracle,
687 // config-type-sqlite
688 foreach ( self::getDBTypes() as $name ) {
689 $allNames[] = wfMessage( "config-type-$name" )->text();
690 }
691
692 $databases = $this->getCompiledDBs();
693
694 $databases = array_flip( $databases );
695 foreach ( array_keys( $databases ) as $db ) {
696 $installer = $this->getDBInstaller( $db );
697 $status = $installer->checkPrerequisites();
698 if ( !$status->isGood() ) {
699 $this->showStatusMessage( $status );
700 }
701 if ( !$status->isOK() ) {
702 unset( $databases[$db] );
703 }
704 }
705 $databases = array_flip( $databases );
706 if ( !$databases ) {
707 $this->showError( 'config-no-db', $wgLang->commaList( $allNames ) );
708
709 // @todo FIXME: This only works for the web installer!
710 return false;
711 }
712
713 return true;
714 }
715
716 /**
717 * Environment check for register_globals.
718 * Prevent installation if enabled
719 */
720 protected function envCheckRegisterGlobals() {
721 if ( wfIniGetBool( 'register_globals' ) ) {
722 $this->showMessage( 'config-register-globals-error' );
723 return false;
724 }
725
726 return true;
727 }
728
729 /**
730 * Some versions of libxml+PHP break < and > encoding horribly
731 * @return bool
732 */
733 protected function envCheckBrokenXML() {
734 $test = new PhpXmlBugTester();
735 if ( !$test->ok ) {
736 $this->showError( 'config-brokenlibxml' );
737
738 return false;
739 }
740
741 return true;
742 }
743
744 /**
745 * Environment check for magic_quotes_runtime.
746 * @return bool
747 */
748 protected function envCheckMagicQuotes() {
749 if ( wfIniGetBool( "magic_quotes_runtime" ) ) {
750 $this->showError( 'config-magic-quotes-runtime' );
751
752 return false;
753 }
754
755 return true;
756 }
757
758 /**
759 * Environment check for magic_quotes_sybase.
760 * @return bool
761 */
762 protected function envCheckMagicSybase() {
763 if ( wfIniGetBool( 'magic_quotes_sybase' ) ) {
764 $this->showError( 'config-magic-quotes-sybase' );
765
766 return false;
767 }
768
769 return true;
770 }
771
772 /**
773 * Environment check for mbstring.func_overload.
774 * @return bool
775 */
776 protected function envCheckMbstring() {
777 if ( wfIniGetBool( 'mbstring.func_overload' ) ) {
778 $this->showError( 'config-mbstring' );
779
780 return false;
781 }
782
783 return true;
784 }
785
786 /**
787 * Environment check for safe_mode.
788 * @return bool
789 */
790 protected function envCheckSafeMode() {
791 if ( wfIniGetBool( 'safe_mode' ) ) {
792 $this->setVar( '_SafeMode', true );
793 $this->showMessage( 'config-safe-mode' );
794 }
795
796 return true;
797 }
798
799 /**
800 * Environment check for the XML module.
801 * @return bool
802 */
803 protected function envCheckXML() {
804 if ( !function_exists( "utf8_encode" ) ) {
805 $this->showError( 'config-xml-bad' );
806
807 return false;
808 }
809
810 return true;
811 }
812
813 /**
814 * Environment check for the PCRE module.
815 *
816 * @note If this check were to fail, the parser would
817 * probably throw an exception before the result
818 * of this check is shown to the user.
819 * @return bool
820 */
821 protected function envCheckPCRE() {
822 wfSuppressWarnings();
823 $regexd = preg_replace( '/[\x{0430}-\x{04FF}]/iu', '', '-АБВГД-' );
824 // Need to check for \p support too, as PCRE can be compiled
825 // with utf8 support, but not unicode property support.
826 // check that \p{Zs} (space separators) matches
827 // U+3000 (Ideographic space)
828 $regexprop = preg_replace( '/\p{Zs}/u', '', "-\xE3\x80\x80-" );
829 wfRestoreWarnings();
830 if ( $regexd != '--' || $regexprop != '--' ) {
831 $this->showError( 'config-pcre-no-utf8' );
832
833 return false;
834 }
835
836 return true;
837 }
838
839 /**
840 * Environment check for available memory.
841 * @return bool
842 */
843 protected function envCheckMemory() {
844 $limit = ini_get( 'memory_limit' );
845
846 if ( !$limit || $limit == -1 ) {
847 return true;
848 }
849
850 $n = wfShorthandToInteger( $limit );
851
852 if ( $n < $this->minMemorySize * 1024 * 1024 ) {
853 $newLimit = "{$this->minMemorySize}M";
854
855 if ( ini_set( "memory_limit", $newLimit ) === false ) {
856 $this->showMessage( 'config-memory-bad', $limit );
857 } else {
858 $this->showMessage( 'config-memory-raised', $limit, $newLimit );
859 $this->setVar( '_RaiseMemory', true );
860 }
861 }
862
863 return true;
864 }
865
866 /**
867 * Environment check for compiled object cache types.
868 */
869 protected function envCheckCache() {
870 $caches = array();
871 foreach ( $this->objectCaches as $name => $function ) {
872 if ( function_exists( $function ) ) {
873 if ( $name == 'xcache' && !wfIniGetBool( 'xcache.var_size' ) ) {
874 continue;
875 }
876 $caches[$name] = true;
877 }
878 }
879
880 if ( !$caches ) {
881 $this->showMessage( 'config-no-cache' );
882 }
883
884 $this->setVar( '_Caches', $caches );
885 }
886
887 /**
888 * Scare user to death if they have mod_security
889 * @return bool
890 */
891 protected function envCheckModSecurity() {
892 if ( self::apacheModulePresent( 'mod_security' ) ) {
893 $this->showMessage( 'config-mod-security' );
894 }
895
896 return true;
897 }
898
899 /**
900 * Search for GNU diff3.
901 * @return bool
902 */
903 protected function envCheckDiff3() {
904 $names = array( "gdiff3", "diff3", "diff3.exe" );
905 $versionInfo = array( '$1 --version 2>&1', 'GNU diffutils' );
906
907 $diff3 = self::locateExecutableInDefaultPaths( $names, $versionInfo );
908
909 if ( $diff3 ) {
910 $this->setVar( 'wgDiff3', $diff3 );
911 } else {
912 $this->setVar( 'wgDiff3', false );
913 $this->showMessage( 'config-diff3-bad' );
914 }
915
916 return true;
917 }
918
919 /**
920 * Environment check for ImageMagick and GD.
921 * @return bool
922 */
923 protected function envCheckGraphics() {
924 $names = array( wfIsWindows() ? 'convert.exe' : 'convert' );
925 $versionInfo = array( '$1 -version', 'ImageMagick' );
926 $convert = self::locateExecutableInDefaultPaths( $names, $versionInfo );
927
928 $this->setVar( 'wgImageMagickConvertCommand', '' );
929 if ( $convert ) {
930 $this->setVar( 'wgImageMagickConvertCommand', $convert );
931 $this->showMessage( 'config-imagemagick', $convert );
932
933 return true;
934 } elseif ( function_exists( 'imagejpeg' ) ) {
935 $this->showMessage( 'config-gd' );
936 } else {
937 $this->showMessage( 'config-no-scaling' );
938 }
939
940 return true;
941 }
942
943 /**
944 * Search for git.
945 *
946 * @since 1.22
947 * @return bool
948 */
949 protected function envCheckGit() {
950 $names = array( wfIsWindows() ? 'git.exe' : 'git' );
951 $versionInfo = array( '$1 --version', 'git version' );
952
953 $git = self::locateExecutableInDefaultPaths( $names, $versionInfo );
954
955 if ( $git ) {
956 $this->setVar( 'wgGitBin', $git );
957 $this->showMessage( 'config-git', $git );
958 } else {
959 $this->setVar( 'wgGitBin', false );
960 $this->showMessage( 'config-git-bad' );
961 }
962
963 return true;
964 }
965
966 /**
967 * Environment check for the server hostname.
968 */
969 protected function envCheckServer() {
970 $server = $this->envGetDefaultServer();
971 if ( $server !== null ) {
972 $this->showMessage( 'config-using-server', $server );
973 $this->setVar( 'wgServer', $server );
974 }
975
976 return true;
977 }
978
979 /**
980 * Helper function to be called from envCheckServer()
981 * @return string
982 */
983 abstract protected function envGetDefaultServer();
984
985 /**
986 * Environment check for setting $IP and $wgScriptPath.
987 * @return bool
988 */
989 protected function envCheckPath() {
990 global $IP;
991 $IP = dirname( dirname( __DIR__ ) );
992 $this->setVar( 'IP', $IP );
993
994 $this->showMessage(
995 'config-using-uri',
996 $this->getVar( 'wgServer' ),
997 $this->getVar( 'wgScriptPath' )
998 );
999
1000 return true;
1001 }
1002
1003 /**
1004 * Environment check for setting the preferred PHP file extension.
1005 * @return bool
1006 */
1007 protected function envCheckExtension() {
1008 // @todo FIXME: Detect this properly
1009 if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) {
1010 $ext = 'php5';
1011 } else {
1012 $ext = 'php';
1013 }
1014 $this->setVar( 'wgScriptExtension', ".$ext" );
1015
1016 return true;
1017 }
1018
1019 /**
1020 * Environment check for preferred locale in shell
1021 * @return bool
1022 */
1023 protected function envCheckShellLocale() {
1024 $os = php_uname( 's' );
1025 $supported = array( 'Linux', 'SunOS', 'HP-UX', 'Darwin' ); # Tested these
1026
1027 if ( !in_array( $os, $supported ) ) {
1028 return true;
1029 }
1030
1031 # Get a list of available locales.
1032 $ret = false;
1033 $lines = wfShellExec( '/usr/bin/locale -a', $ret );
1034
1035 if ( $ret ) {
1036 return true;
1037 }
1038
1039 $lines = array_map( 'trim', explode( "\n", $lines ) );
1040 $candidatesByLocale = array();
1041 $candidatesByLang = array();
1042
1043 foreach ( $lines as $line ) {
1044 if ( $line === '' ) {
1045 continue;
1046 }
1047
1048 if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) {
1049 continue;
1050 }
1051
1052 list( , $lang, , , ) = $m;
1053
1054 $candidatesByLocale[$m[0]] = $m;
1055 $candidatesByLang[$lang][] = $m;
1056 }
1057
1058 # Try the current value of LANG.
1059 if ( isset( $candidatesByLocale[getenv( 'LANG' )] ) ) {
1060 $this->setVar( 'wgShellLocale', getenv( 'LANG' ) );
1061
1062 return true;
1063 }
1064
1065 # Try the most common ones.
1066 $commonLocales = array( 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' );
1067 foreach ( $commonLocales as $commonLocale ) {
1068 if ( isset( $candidatesByLocale[$commonLocale] ) ) {
1069 $this->setVar( 'wgShellLocale', $commonLocale );
1070
1071 return true;
1072 }
1073 }
1074
1075 # Is there an available locale in the Wiki's language?
1076 $wikiLang = $this->getVar( 'wgLanguageCode' );
1077
1078 if ( isset( $candidatesByLang[$wikiLang] ) ) {
1079 $m = reset( $candidatesByLang[$wikiLang] );
1080 $this->setVar( 'wgShellLocale', $m[0] );
1081
1082 return true;
1083 }
1084
1085 # Are there any at all?
1086 if ( count( $candidatesByLocale ) ) {
1087 $m = reset( $candidatesByLocale );
1088 $this->setVar( 'wgShellLocale', $m[0] );
1089
1090 return true;
1091 }
1092
1093 # Give up.
1094 return true;
1095 }
1096
1097 /**
1098 * Environment check for the permissions of the uploads directory
1099 * @return bool
1100 */
1101 protected function envCheckUploadsDirectory() {
1102 global $IP;
1103
1104 $dir = $IP . '/images/';
1105 $url = $this->getVar( 'wgServer' ) . $this->getVar( 'wgScriptPath' ) . '/images/';
1106 $safe = !$this->dirIsExecutable( $dir, $url );
1107
1108 if ( !$safe ) {
1109 $this->showMessage( 'config-uploads-not-safe', $dir );
1110 }
1111
1112 return true;
1113 }
1114
1115 /**
1116 * Checks if suhosin.get.max_value_length is set, and if so generate
1117 * a warning because it decreases ResourceLoader performance.
1118 * @return bool
1119 */
1120 protected function envCheckSuhosinMaxValueLength() {
1121 $maxValueLength = ini_get( 'suhosin.get.max_value_length' );
1122 if ( $maxValueLength > 0 && $maxValueLength < 1024 ) {
1123 // Only warn if the value is below the sane 1024
1124 $this->showMessage( 'config-suhosin-max-value-length', $maxValueLength );
1125 }
1126
1127 return true;
1128 }
1129
1130 /**
1131 * Convert a hex string representing a Unicode code point to that code point.
1132 * @param string $c
1133 * @return string
1134 */
1135 protected function unicodeChar( $c ) {
1136 $c = hexdec( $c );
1137 if ( $c <= 0x7F ) {
1138 return chr( $c );
1139 } elseif ( $c <= 0x7FF ) {
1140 return chr( 0xC0 | $c >> 6 ) . chr( 0x80 | $c & 0x3F );
1141 } elseif ( $c <= 0xFFFF ) {
1142 return chr( 0xE0 | $c >> 12 ) . chr( 0x80 | $c >> 6 & 0x3F )
1143 . chr( 0x80 | $c & 0x3F );
1144 } elseif ( $c <= 0x10FFFF ) {
1145 return chr( 0xF0 | $c >> 18 ) . chr( 0x80 | $c >> 12 & 0x3F )
1146 . chr( 0x80 | $c >> 6 & 0x3F )
1147 . chr( 0x80 | $c & 0x3F );
1148 } else {
1149 return false;
1150 }
1151 }
1152
1153 /**
1154 * Check the libicu version
1155 */
1156 protected function envCheckLibicu() {
1157 $utf8 = function_exists( 'utf8_normalize' );
1158 $intl = function_exists( 'normalizer_normalize' );
1159
1160 /**
1161 * This needs to be updated something that the latest libicu
1162 * will properly normalize. This normalization was found at
1163 * http://www.unicode.org/versions/Unicode5.2.0/#Character_Additions
1164 * Note that we use the hex representation to create the code
1165 * points in order to avoid any Unicode-destroying during transit.
1166 */
1167 $not_normal_c = $this->unicodeChar( "FA6C" );
1168 $normal_c = $this->unicodeChar( "242EE" );
1169
1170 $useNormalizer = 'php';
1171 $needsUpdate = false;
1172
1173 /**
1174 * We're going to prefer the pecl extension here unless
1175 * utf8_normalize is more up to date.
1176 */
1177 if ( $utf8 ) {
1178 $useNormalizer = 'utf8';
1179 $utf8 = utf8_normalize( $not_normal_c, UtfNormal::UNORM_NFC );
1180 if ( $utf8 !== $normal_c ) {
1181 $needsUpdate = true;
1182 }
1183 }
1184 if ( $intl ) {
1185 $useNormalizer = 'intl';
1186 $intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C );
1187 if ( $intl !== $normal_c ) {
1188 $needsUpdate = true;
1189 }
1190 }
1191
1192 // Uses messages 'config-unicode-using-php', 'config-unicode-using-utf8',
1193 // 'config-unicode-using-intl'
1194 if ( $useNormalizer === 'php' ) {
1195 $this->showMessage( 'config-unicode-pure-php-warning' );
1196 } else {
1197 $this->showMessage( 'config-unicode-using-' . $useNormalizer );
1198 if ( $needsUpdate ) {
1199 $this->showMessage( 'config-unicode-update-warning' );
1200 }
1201 }
1202 }
1203
1204 /**
1205 * @return bool
1206 */
1207 protected function envCheckCtype() {
1208 if ( !function_exists( 'ctype_digit' ) ) {
1209 $this->showError( 'config-ctype' );
1210
1211 return false;
1212 }
1213
1214 return true;
1215 }
1216
1217 /**
1218 * @return bool
1219 */
1220 protected function envCheckJSON() {
1221 if ( !function_exists( 'json_decode' ) ) {
1222 $this->showError( 'config-json' );
1223
1224 return false;
1225 }
1226
1227 return true;
1228 }
1229
1230 /**
1231 * Get an array of likely places we can find executables. Check a bunch
1232 * of known Unix-like defaults, as well as the PATH environment variable
1233 * (which should maybe make it work for Windows?)
1234 *
1235 * @return array
1236 */
1237 protected static function getPossibleBinPaths() {
1238 return array_merge(
1239 array( '/usr/bin', '/usr/local/bin', '/opt/csw/bin',
1240 '/usr/gnu/bin', '/usr/sfw/bin', '/sw/bin', '/opt/local/bin' ),
1241 explode( PATH_SEPARATOR, getenv( 'PATH' ) )
1242 );
1243 }
1244
1245 /**
1246 * Search a path for any of the given executable names. Returns the
1247 * executable name if found. Also checks the version string returned
1248 * by each executable.
1249 *
1250 * Used only by environment checks.
1251 *
1252 * @param string $path path to search
1253 * @param array $names of executable names
1254 * @param array|bool $versionInfo False or array with two members:
1255 * 0 => Command to run for version check, with $1 for the full executable name
1256 * 1 => String to compare the output with
1257 *
1258 * If $versionInfo is not false, only executables with a version
1259 * matching $versionInfo[1] will be returned.
1260 * @return bool|string
1261 */
1262 public static function locateExecutable( $path, $names, $versionInfo = false ) {
1263 if ( !is_array( $names ) ) {
1264 $names = array( $names );
1265 }
1266
1267 foreach ( $names as $name ) {
1268 $command = $path . DIRECTORY_SEPARATOR . $name;
1269
1270 wfSuppressWarnings();
1271 $file_exists = file_exists( $command );
1272 wfRestoreWarnings();
1273
1274 if ( $file_exists ) {
1275 if ( !$versionInfo ) {
1276 return $command;
1277 }
1278
1279 $file = str_replace( '$1', wfEscapeShellArg( $command ), $versionInfo[0] );
1280 if ( strstr( wfShellExec( $file ), $versionInfo[1] ) !== false ) {
1281 return $command;
1282 }
1283 }
1284 }
1285
1286 return false;
1287 }
1288
1289 /**
1290 * Same as locateExecutable(), but checks in getPossibleBinPaths() by default
1291 * @see locateExecutable()
1292 * @param array $names Array of possible names.
1293 * @param array|bool $versionInfo Default: false or array with two members:
1294 * 0 => Command to run for version check, with $1 for the full executable name
1295 * 1 => String to compare the output with
1296 *
1297 * If $versionInfo is not false, only executables with a version
1298 * matching $versionInfo[1] will be returned.
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 string $dir
1317 * @param string $url
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 Language $lang
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 string $page
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 DatabaseInstaller $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 callable $startCB A callback array for the beginning of each step
1528 * @param callable $endCB A callback array for the end of each step
1529 *
1530 * @return array 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 array $keys
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 Status $s
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 DatabaseInstaller $installer
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 // Don't bother embedding images into generated CSS, which is not cached
1735 $GLOBALS['wgResourceLoaderLESSFunctions']['embeddable'] = function ( $frame, $less ) {
1736 return $less->toBool( false );
1737 };
1738 }
1739
1740 /**
1741 * Add an installation step following the given step.
1742 *
1743 * @param callable $callback A valid installation callback array, in this form:
1744 * array( 'name' => 'some-unique-name', 'callback' => array( $obj, 'function' ) );
1745 * @param string $findStep The step to find. Omit to put the step at the beginning
1746 */
1747 public function addInstallStep( $callback, $findStep = 'BEGINNING' ) {
1748 $this->extraInstallSteps[$findStep][] = $callback;
1749 }
1750
1751 /**
1752 * Disable the time limit for execution.
1753 * Some long-running pages (Install, Upgrade) will want to do this
1754 */
1755 protected function disableTimeLimit() {
1756 wfSuppressWarnings();
1757 set_time_limit( 0 );
1758 wfRestoreWarnings();
1759 }
1760 }