Merge "Update type hints in TraditionalImageGallery"
[lhc/web/wiklou.git] / includes / installer / Installer.php
1 <?php
2 /**
3 * Base code for MediaWiki installer.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @ingroup Deployment
22 */
23
24 /**
25 * This documentation group collects source code files with deployment functionality.
26 *
27 * @defgroup Deployment Deployment
28 */
29
30 /**
31 * Base installer class.
32 *
33 * This class provides the base for installation and update functionality
34 * for both MediaWiki core and extensions.
35 *
36 * @ingroup Deployment
37 * @since 1.17
38 */
39 abstract class Installer {
40
41 // This is the absolute minimum PHP version we can support
42 const MINIMUM_PHP_VERSION = '5.3.2';
43
44 /**
45 * @var array
46 */
47 protected $settings;
48
49 /**
50 * List of detected DBs, access using getCompiledDBs().
51 *
52 * @var array
53 */
54 protected $compiledDBs;
55
56 /**
57 * Cached DB installer instances, access using getDBInstaller().
58 *
59 * @var array
60 */
61 protected $dbInstallers = array();
62
63 /**
64 * Minimum memory size in MB.
65 *
66 * @var integer
67 */
68 protected $minMemorySize = 50;
69
70 /**
71 * Cached Title, used by parse().
72 *
73 * @var Title
74 */
75 protected $parserTitle;
76
77 /**
78 * Cached ParserOptions, used by parse().
79 *
80 * @var ParserOptions
81 */
82 protected $parserOptions;
83
84 /**
85 * Known database types. These correspond to the class names <type>Installer,
86 * and are also MediaWiki database types valid for $wgDBtype.
87 *
88 * To add a new type, create a <type>Installer class and a Database<type>
89 * class, and add a config-type-<type> message to MessagesEn.php.
90 *
91 * @var array
92 */
93 protected static $dbTypes = array(
94 'mysql',
95 'postgres',
96 'oracle',
97 'sqlite',
98 );
99
100 /**
101 * A list of environment check methods called by doEnvironmentChecks().
102 * These may output warnings using showMessage(), and/or abort the
103 * installation process by returning false.
104 *
105 * @var array
106 */
107 protected $envChecks = array(
108 'envCheckDB',
109 'envCheckRegisterGlobals',
110 'envCheckBrokenXML',
111 'envCheckPHP531',
112 'envCheckMagicQuotes',
113 'envCheckMagicSybase',
114 'envCheckMbstring',
115 'envCheckZE1',
116 'envCheckSafeMode',
117 'envCheckXML',
118 'envCheckPCRE',
119 'envCheckMemory',
120 'envCheckCache',
121 'envCheckModSecurity',
122 'envCheckDiff3',
123 'envCheckGraphics',
124 'envCheckGit',
125 'envCheckServer',
126 'envCheckPath',
127 'envCheckExtension',
128 'envCheckShellLocale',
129 'envCheckUploadsDirectory',
130 'envCheckLibicu',
131 'envCheckSuhosinMaxValueLength',
132 'envCheckCtype',
133 'envCheckJSON',
134 );
135
136 /**
137 * MediaWiki configuration globals that will eventually be passed through
138 * to LocalSettings.php. The names only are given here, the defaults
139 * typically come from DefaultSettings.php.
140 *
141 * @var array
142 */
143 protected $defaultVarNames = array(
144 'wgSitename',
145 'wgPasswordSender',
146 'wgLanguageCode',
147 'wgRightsIcon',
148 'wgRightsText',
149 'wgRightsUrl',
150 'wgMainCacheType',
151 'wgEnableEmail',
152 'wgEnableUserEmail',
153 'wgEnotifUserTalk',
154 'wgEnotifWatchlist',
155 'wgEmailAuthentication',
156 'wgDBtype',
157 'wgDiff3',
158 'wgImageMagickConvertCommand',
159 'wgGitBin',
160 'IP',
161 'wgScriptPath',
162 'wgScriptExtension',
163 'wgMetaNamespace',
164 'wgDeletedDirectory',
165 'wgEnableUploads',
166 'wgLogo',
167 'wgShellLocale',
168 'wgSecretKey',
169 'wgUseInstantCommons',
170 'wgUpgradeKey',
171 'wgDefaultSkin',
172 'wgResourceLoaderMaxQueryLength',
173 );
174
175 /**
176 * Variables that are stored alongside globals, and are used for any
177 * configuration of the installation process aside from the MediaWiki
178 * configuration. Map of names to defaults.
179 *
180 * @var array
181 */
182 protected $internalDefaults = array(
183 '_UserLang' => 'en',
184 '_Environment' => false,
185 '_SafeMode' => false,
186 '_RaiseMemory' => false,
187 '_UpgradeDone' => false,
188 '_InstallDone' => false,
189 '_Caches' => array(),
190 '_InstallPassword' => '',
191 '_SameAccount' => true,
192 '_CreateDBAccount' => false,
193 '_NamespaceType' => 'site-name',
194 '_AdminName' => '', // will be set later, when the user selects language
195 '_AdminPassword' => '',
196 '_AdminPassword2' => '',
197 '_AdminEmail' => '',
198 '_Subscribe' => false,
199 '_SkipOptional' => 'continue',
200 '_RightsProfile' => 'wiki',
201 '_LicenseCode' => 'none',
202 '_CCDone' => false,
203 '_Extensions' => array(),
204 '_MemCachedServers' => '',
205 '_UpgradeKeySupplied' => false,
206 '_ExistingDBSettings' => false,
207 );
208
209 /**
210 * The actual list of installation steps. This will be initialized by getInstallSteps()
211 *
212 * @var array
213 */
214 private $installSteps = array();
215
216 /**
217 * Extra steps for installation, for things like DatabaseInstallers to modify
218 *
219 * @var array
220 */
221 protected $extraInstallSteps = array();
222
223 /**
224 * Known object cache types and the functions used to test for their existence.
225 *
226 * @var array
227 */
228 protected $objectCaches = array(
229 'xcache' => 'xcache_get',
230 'apc' => 'apc_fetch',
231 'wincache' => 'wincache_ucache_get'
232 );
233
234 /**
235 * User rights profiles.
236 *
237 * @var array
238 */
239 public $rightsProfiles = array(
240 'wiki' => array(),
241 'no-anon' => array(
242 '*' => array( 'edit' => false )
243 ),
244 'fishbowl' => array(
245 '*' => array(
246 'createaccount' => false,
247 'edit' => false,
248 ),
249 ),
250 'private' => array(
251 '*' => array(
252 'createaccount' => false,
253 'edit' => false,
254 'read' => false,
255 ),
256 ),
257 );
258
259 /**
260 * License types.
261 *
262 * @var array
263 */
264 public $licenses = array(
265 'cc-by' => array(
266 'url' => 'http://creativecommons.org/licenses/by/3.0/',
267 'icon' => '{$wgStylePath}/common/images/cc-by.png',
268 ),
269 'cc-by-sa' => array(
270 'url' => 'http://creativecommons.org/licenses/by-sa/3.0/',
271 'icon' => '{$wgStylePath}/common/images/cc-by-sa.png',
272 ),
273 'cc-by-nc-sa' => array(
274 'url' => 'http://creativecommons.org/licenses/by-nc-sa/3.0/',
275 'icon' => '{$wgStylePath}/common/images/cc-by-nc-sa.png',
276 ),
277 'cc-0' => array(
278 'url' => 'https://creativecommons.org/publicdomain/zero/1.0/',
279 'icon' => '{$wgStylePath}/common/images/cc-0.png',
280 ),
281 'pd' => array(
282 'url' => '',
283 'icon' => '{$wgStylePath}/common/images/public-domain.png',
284 ),
285 'gfdl' => array(
286 'url' => 'http://www.gnu.org/copyleft/fdl.html',
287 'icon' => '{$wgStylePath}/common/images/gnu-fdl.png',
288 ),
289 'none' => array(
290 'url' => '',
291 'icon' => '',
292 'text' => ''
293 ),
294 'cc-choose' => array(
295 // Details will be filled in by the selector.
296 'url' => '',
297 'icon' => '',
298 'text' => '',
299 ),
300 );
301
302 /**
303 * URL to mediawiki-announce subscription
304 */
305 protected $mediaWikiAnnounceUrl =
306 '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 if ( $server !== null ) {
984 $this->showMessage( 'config-using-server', $server );
985 $this->setVar( 'wgServer', $server );
986 }
987
988 return true;
989 }
990
991 /**
992 * Helper function to be called from envCheckServer()
993 * @return String
994 */
995 abstract protected function envGetDefaultServer();
996
997 /**
998 * Environment check for setting $IP and $wgScriptPath.
999 * @return bool
1000 */
1001 protected function envCheckPath() {
1002 global $IP;
1003 $IP = dirname( dirname( __DIR__ ) );
1004 $this->setVar( 'IP', $IP );
1005
1006 $this->showMessage(
1007 'config-using-uri',
1008 $this->getVar( 'wgServer' ),
1009 $this->getVar( 'wgScriptPath' )
1010 );
1011
1012 return true;
1013 }
1014
1015 /**
1016 * Environment check for setting the preferred PHP file extension.
1017 * @return bool
1018 */
1019 protected function envCheckExtension() {
1020 // @todo FIXME: Detect this properly
1021 if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) {
1022 $ext = 'php5';
1023 } else {
1024 $ext = 'php';
1025 }
1026 $this->setVar( 'wgScriptExtension', ".$ext" );
1027
1028 return true;
1029 }
1030
1031 /**
1032 * Environment check for preferred locale in shell
1033 * @return bool
1034 */
1035 protected function envCheckShellLocale() {
1036 $os = php_uname( 's' );
1037 $supported = array( 'Linux', 'SunOS', 'HP-UX', 'Darwin' ); # Tested these
1038
1039 if ( !in_array( $os, $supported ) ) {
1040 return true;
1041 }
1042
1043 # Get a list of available locales.
1044 $ret = false;
1045 $lines = wfShellExec( '/usr/bin/locale -a', $ret );
1046
1047 if ( $ret ) {
1048 return true;
1049 }
1050
1051 $lines = array_map( 'trim', explode( "\n", $lines ) );
1052 $candidatesByLocale = array();
1053 $candidatesByLang = array();
1054
1055 foreach ( $lines as $line ) {
1056 if ( $line === '' ) {
1057 continue;
1058 }
1059
1060 if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) {
1061 continue;
1062 }
1063
1064 list( , $lang, , , ) = $m;
1065
1066 $candidatesByLocale[$m[0]] = $m;
1067 $candidatesByLang[$lang][] = $m;
1068 }
1069
1070 # Try the current value of LANG.
1071 if ( isset( $candidatesByLocale[getenv( 'LANG' )] ) ) {
1072 $this->setVar( 'wgShellLocale', getenv( 'LANG' ) );
1073
1074 return true;
1075 }
1076
1077 # Try the most common ones.
1078 $commonLocales = array( 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' );
1079 foreach ( $commonLocales as $commonLocale ) {
1080 if ( isset( $candidatesByLocale[$commonLocale] ) ) {
1081 $this->setVar( 'wgShellLocale', $commonLocale );
1082
1083 return true;
1084 }
1085 }
1086
1087 # Is there an available locale in the Wiki's language?
1088 $wikiLang = $this->getVar( 'wgLanguageCode' );
1089
1090 if ( isset( $candidatesByLang[$wikiLang] ) ) {
1091 $m = reset( $candidatesByLang[$wikiLang] );
1092 $this->setVar( 'wgShellLocale', $m[0] );
1093
1094 return true;
1095 }
1096
1097 # Are there any at all?
1098 if ( count( $candidatesByLocale ) ) {
1099 $m = reset( $candidatesByLocale );
1100 $this->setVar( 'wgShellLocale', $m[0] );
1101
1102 return true;
1103 }
1104
1105 # Give up.
1106 return true;
1107 }
1108
1109 /**
1110 * Environment check for the permissions of the uploads directory
1111 * @return bool
1112 */
1113 protected function envCheckUploadsDirectory() {
1114 global $IP;
1115
1116 $dir = $IP . '/images/';
1117 $url = $this->getVar( 'wgServer' ) . $this->getVar( 'wgScriptPath' ) . '/images/';
1118 $safe = !$this->dirIsExecutable( $dir, $url );
1119
1120 if ( !$safe ) {
1121 $this->showMessage( 'config-uploads-not-safe', $dir );
1122 }
1123
1124 return true;
1125 }
1126
1127 /**
1128 * Checks if suhosin.get.max_value_length is set, and if so generate
1129 * a warning because it decreases ResourceLoader performance.
1130 * @return bool
1131 */
1132 protected function envCheckSuhosinMaxValueLength() {
1133 $maxValueLength = ini_get( 'suhosin.get.max_value_length' );
1134 if ( $maxValueLength > 0 && $maxValueLength < 1024 ) {
1135 // Only warn if the value is below the sane 1024
1136 $this->showMessage( 'config-suhosin-max-value-length', $maxValueLength );
1137 }
1138
1139 return true;
1140 }
1141
1142 /**
1143 * Convert a hex string representing a Unicode code point to that code point.
1144 * @param $c String
1145 * @return string
1146 */
1147 protected function unicodeChar( $c ) {
1148 $c = hexdec( $c );
1149 if ( $c <= 0x7F ) {
1150 return chr( $c );
1151 } elseif ( $c <= 0x7FF ) {
1152 return chr( 0xC0 | $c >> 6 ) . chr( 0x80 | $c & 0x3F );
1153 } elseif ( $c <= 0xFFFF ) {
1154 return chr( 0xE0 | $c >> 12 ) . chr( 0x80 | $c >> 6 & 0x3F )
1155 . chr( 0x80 | $c & 0x3F );
1156 } elseif ( $c <= 0x10FFFF ) {
1157 return chr( 0xF0 | $c >> 18 ) . chr( 0x80 | $c >> 12 & 0x3F )
1158 . chr( 0x80 | $c >> 6 & 0x3F )
1159 . chr( 0x80 | $c & 0x3F );
1160 } else {
1161 return false;
1162 }
1163 }
1164
1165 /**
1166 * Check the libicu version
1167 */
1168 protected function envCheckLibicu() {
1169 $utf8 = function_exists( 'utf8_normalize' );
1170 $intl = function_exists( 'normalizer_normalize' );
1171
1172 /**
1173 * This needs to be updated something that the latest libicu
1174 * will properly normalize. This normalization was found at
1175 * http://www.unicode.org/versions/Unicode5.2.0/#Character_Additions
1176 * Note that we use the hex representation to create the code
1177 * points in order to avoid any Unicode-destroying during transit.
1178 */
1179 $not_normal_c = $this->unicodeChar( "FA6C" );
1180 $normal_c = $this->unicodeChar( "242EE" );
1181
1182 $useNormalizer = 'php';
1183 $needsUpdate = false;
1184
1185 /**
1186 * We're going to prefer the pecl extension here unless
1187 * utf8_normalize is more up to date.
1188 */
1189 if ( $utf8 ) {
1190 $useNormalizer = 'utf8';
1191 $utf8 = utf8_normalize( $not_normal_c, UtfNormal::UNORM_NFC );
1192 if ( $utf8 !== $normal_c ) {
1193 $needsUpdate = true;
1194 }
1195 }
1196 if ( $intl ) {
1197 $useNormalizer = 'intl';
1198 $intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C );
1199 if ( $intl !== $normal_c ) {
1200 $needsUpdate = true;
1201 }
1202 }
1203
1204 // Uses messages 'config-unicode-using-php', 'config-unicode-using-utf8',
1205 // 'config-unicode-using-intl'
1206 if ( $useNormalizer === 'php' ) {
1207 $this->showMessage( 'config-unicode-pure-php-warning' );
1208 } else {
1209 $this->showMessage( 'config-unicode-using-' . $useNormalizer );
1210 if ( $needsUpdate ) {
1211 $this->showMessage( 'config-unicode-update-warning' );
1212 }
1213 }
1214 }
1215
1216 /**
1217 * @return bool
1218 */
1219 protected function envCheckCtype() {
1220 if ( !function_exists( 'ctype_digit' ) ) {
1221 $this->showError( 'config-ctype' );
1222
1223 return false;
1224 }
1225
1226 return true;
1227 }
1228
1229 /**
1230 * @return bool
1231 */
1232 protected function envCheckJSON() {
1233 if ( !function_exists( 'json_decode' ) ) {
1234 $this->showError( 'config-json' );
1235
1236 return false;
1237 }
1238
1239 return true;
1240 }
1241
1242 /**
1243 * Get an array of likely places we can find executables. Check a bunch
1244 * of known Unix-like defaults, as well as the PATH environment variable
1245 * (which should maybe make it work for Windows?)
1246 *
1247 * @return Array
1248 */
1249 protected static function getPossibleBinPaths() {
1250 return array_merge(
1251 array( '/usr/bin', '/usr/local/bin', '/opt/csw/bin',
1252 '/usr/gnu/bin', '/usr/sfw/bin', '/sw/bin', '/opt/local/bin' ),
1253 explode( PATH_SEPARATOR, getenv( 'PATH' ) )
1254 );
1255 }
1256
1257 /**
1258 * Search a path for any of the given executable names. Returns the
1259 * executable name if found. Also checks the version string returned
1260 * by each executable.
1261 *
1262 * Used only by environment checks.
1263 *
1264 * @param string $path path to search
1265 * @param array $names of executable names
1266 * @param $versionInfo Boolean false or array with two members:
1267 * 0 => Command to run for version check, with $1 for the full executable name
1268 * 1 => String to compare the output with
1269 *
1270 * If $versionInfo is not false, only executables with a version
1271 * matching $versionInfo[1] will be returned.
1272 * @return bool|string
1273 */
1274 public static function locateExecutable( $path, $names, $versionInfo = false ) {
1275 if ( !is_array( $names ) ) {
1276 $names = array( $names );
1277 }
1278
1279 foreach ( $names as $name ) {
1280 $command = $path . DIRECTORY_SEPARATOR . $name;
1281
1282 wfSuppressWarnings();
1283 $file_exists = file_exists( $command );
1284 wfRestoreWarnings();
1285
1286 if ( $file_exists ) {
1287 if ( !$versionInfo ) {
1288 return $command;
1289 }
1290
1291 $file = str_replace( '$1', wfEscapeShellArg( $command ), $versionInfo[0] );
1292 if ( strstr( wfShellExec( $file ), $versionInfo[1] ) !== false ) {
1293 return $command;
1294 }
1295 }
1296 }
1297
1298 return false;
1299 }
1300
1301 /**
1302 * Same as locateExecutable(), but checks in getPossibleBinPaths() by default
1303 * @see locateExecutable()
1304 * @param array $names Array of possible names.
1305 * @param array|bool $versionInfo Default: false or array with two members:
1306 * 0 => Command to run for version check, with $1 for the full executable name
1307 * 1 => String to compare the output with
1308 *
1309 * If $versionInfo is not false, only executables with a version
1310 * matching $versionInfo[1] will be returned.
1311 * @return bool|string
1312 */
1313 public static function locateExecutableInDefaultPaths( $names, $versionInfo = false ) {
1314 foreach ( self::getPossibleBinPaths() as $path ) {
1315 $exe = self::locateExecutable( $path, $names, $versionInfo );
1316 if ( $exe !== false ) {
1317 return $exe;
1318 }
1319 }
1320
1321 return false;
1322 }
1323
1324 /**
1325 * Checks if scripts located in the given directory can be executed via the given URL.
1326 *
1327 * Used only by environment checks.
1328 * @param $dir string
1329 * @param $url string
1330 * @return bool|int|string
1331 */
1332 public function dirIsExecutable( $dir, $url ) {
1333 $scriptTypes = array(
1334 'php' => array(
1335 "<?php echo 'ex' . 'ec';",
1336 "#!/var/env php5\n<?php echo 'ex' . 'ec';",
1337 ),
1338 );
1339
1340 // it would be good to check other popular languages here, but it'll be slow.
1341
1342 wfSuppressWarnings();
1343
1344 foreach ( $scriptTypes as $ext => $contents ) {
1345 foreach ( $contents as $source ) {
1346 $file = 'exectest.' . $ext;
1347
1348 if ( !file_put_contents( $dir . $file, $source ) ) {
1349 break;
1350 }
1351
1352 try {
1353 $text = Http::get( $url . $file, array( 'timeout' => 3 ) );
1354 } catch ( MWException $e ) {
1355 // Http::get throws with allow_url_fopen = false and no curl extension.
1356 $text = null;
1357 }
1358 unlink( $dir . $file );
1359
1360 if ( $text == 'exec' ) {
1361 wfRestoreWarnings();
1362
1363 return $ext;
1364 }
1365 }
1366 }
1367
1368 wfRestoreWarnings();
1369
1370 return false;
1371 }
1372
1373 /**
1374 * Checks for presence of an Apache module. Works only if PHP is running as an Apache module, too.
1375 *
1376 * @param string $moduleName Name of module to check.
1377 * @return bool
1378 */
1379 public static function apacheModulePresent( $moduleName ) {
1380 if ( function_exists( 'apache_get_modules' ) && in_array( $moduleName, apache_get_modules() ) ) {
1381 return true;
1382 }
1383 // try it the hard way
1384 ob_start();
1385 phpinfo( INFO_MODULES );
1386 $info = ob_get_clean();
1387
1388 return strpos( $info, $moduleName ) !== false;
1389 }
1390
1391 /**
1392 * ParserOptions are constructed before we determined the language, so fix it
1393 *
1394 * @param $lang Language
1395 */
1396 public function setParserLanguage( $lang ) {
1397 $this->parserOptions->setTargetLanguage( $lang );
1398 $this->parserOptions->setUserLang( $lang );
1399 }
1400
1401 /**
1402 * Overridden by WebInstaller to provide lastPage parameters.
1403 * @param $page string
1404 * @return string
1405 */
1406 protected function getDocUrl( $page ) {
1407 return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page );
1408 }
1409
1410 /**
1411 * Finds extensions that follow the format /extensions/Name/Name.php,
1412 * and returns an array containing the value for 'Name' for each found extension.
1413 *
1414 * @return array
1415 */
1416 public function findExtensions() {
1417 if ( $this->getVar( 'IP' ) === null ) {
1418 return array();
1419 }
1420
1421 $extDir = $this->getVar( 'IP' ) . '/extensions';
1422 if ( !is_readable( $extDir ) || !is_dir( $extDir ) ) {
1423 return array();
1424 }
1425
1426 $dh = opendir( $extDir );
1427 $exts = array();
1428 while ( ( $file = readdir( $dh ) ) !== false ) {
1429 if ( !is_dir( "$extDir/$file" ) ) {
1430 continue;
1431 }
1432 if ( file_exists( "$extDir/$file/$file.php" ) ) {
1433 $exts[] = $file;
1434 }
1435 }
1436 closedir( $dh );
1437 natcasesort( $exts );
1438
1439 return $exts;
1440 }
1441
1442 /**
1443 * Installs the auto-detected extensions.
1444 *
1445 * @return Status
1446 */
1447 protected function includeExtensions() {
1448 global $IP;
1449 $exts = $this->getVar( '_Extensions' );
1450 $IP = $this->getVar( 'IP' );
1451
1452 /**
1453 * We need to include DefaultSettings before including extensions to avoid
1454 * warnings about unset variables. However, the only thing we really
1455 * want here is $wgHooks['LoadExtensionSchemaUpdates']. This won't work
1456 * if the extension has hidden hook registration in $wgExtensionFunctions,
1457 * but we're not opening that can of worms
1458 * @see https://bugzilla.wikimedia.org/show_bug.cgi?id=26857
1459 */
1460 global $wgAutoloadClasses;
1461 $wgAutoloadClasses = array();
1462
1463 require "$IP/includes/DefaultSettings.php";
1464
1465 foreach ( $exts as $e ) {
1466 require_once "$IP/extensions/$e/$e.php";
1467 }
1468
1469 $hooksWeWant = isset( $wgHooks['LoadExtensionSchemaUpdates'] ) ?
1470 $wgHooks['LoadExtensionSchemaUpdates'] : array();
1471
1472 // Unset everyone else's hooks. Lord knows what someone might be doing
1473 // in ParserFirstCallInit (see bug 27171)
1474 $GLOBALS['wgHooks'] = array( 'LoadExtensionSchemaUpdates' => $hooksWeWant );
1475
1476 return Status::newGood();
1477 }
1478
1479 /**
1480 * Get an array of install steps. Should always be in the format of
1481 * array(
1482 * 'name' => 'someuniquename',
1483 * 'callback' => array( $obj, 'method' ),
1484 * )
1485 * There must be a config-install-$name message defined per step, which will
1486 * be shown on install.
1487 *
1488 * @param $installer DatabaseInstaller so we can make callbacks
1489 * @return array
1490 */
1491 protected function getInstallSteps( DatabaseInstaller $installer ) {
1492 $coreInstallSteps = array(
1493 array( 'name' => 'database', 'callback' => array( $installer, 'setupDatabase' ) ),
1494 array( 'name' => 'tables', 'callback' => array( $installer, 'createTables' ) ),
1495 array( 'name' => 'interwiki', 'callback' => array( $installer, 'populateInterwikiTable' ) ),
1496 array( 'name' => 'stats', 'callback' => array( $this, 'populateSiteStats' ) ),
1497 array( 'name' => 'keys', 'callback' => array( $this, 'generateKeys' ) ),
1498 array( 'name' => 'sysop', 'callback' => array( $this, 'createSysop' ) ),
1499 array( 'name' => 'mainpage', 'callback' => array( $this, 'createMainpage' ) ),
1500 );
1501
1502 // Build the array of install steps starting from the core install list,
1503 // then adding any callbacks that wanted to attach after a given step
1504 foreach ( $coreInstallSteps as $step ) {
1505 $this->installSteps[] = $step;
1506 if ( isset( $this->extraInstallSteps[$step['name']] ) ) {
1507 $this->installSteps = array_merge(
1508 $this->installSteps,
1509 $this->extraInstallSteps[$step['name']]
1510 );
1511 }
1512 }
1513
1514 // Prepend any steps that want to be at the beginning
1515 if ( isset( $this->extraInstallSteps['BEGINNING'] ) ) {
1516 $this->installSteps = array_merge(
1517 $this->extraInstallSteps['BEGINNING'],
1518 $this->installSteps
1519 );
1520 }
1521
1522 // Extensions should always go first, chance to tie into hooks and such
1523 if ( count( $this->getVar( '_Extensions' ) ) ) {
1524 array_unshift( $this->installSteps,
1525 array( 'name' => 'extensions', 'callback' => array( $this, 'includeExtensions' ) )
1526 );
1527 $this->installSteps[] = array(
1528 'name' => 'extension-tables',
1529 'callback' => array( $installer, 'createExtensionTables' )
1530 );
1531 }
1532
1533 return $this->installSteps;
1534 }
1535
1536 /**
1537 * Actually perform the installation.
1538 *
1539 * @param array $startCB A callback array for the beginning of each step
1540 * @param array $endCB A callback array for the end of each step
1541 *
1542 * @return Array of Status objects
1543 */
1544 public function performInstallation( $startCB, $endCB ) {
1545 $installResults = array();
1546 $installer = $this->getDBInstaller();
1547 $installer->preInstall();
1548 $steps = $this->getInstallSteps( $installer );
1549 foreach ( $steps as $stepObj ) {
1550 $name = $stepObj['name'];
1551 call_user_func_array( $startCB, array( $name ) );
1552
1553 // Perform the callback step
1554 $status = call_user_func( $stepObj['callback'], $installer );
1555
1556 // Output and save the results
1557 call_user_func( $endCB, $name, $status );
1558 $installResults[$name] = $status;
1559
1560 // If we've hit some sort of fatal, we need to bail.
1561 // Callback already had a chance to do output above.
1562 if ( !$status->isOk() ) {
1563 break;
1564 }
1565 }
1566 if ( $status->isOk() ) {
1567 $this->setVar( '_InstallDone', true );
1568 }
1569
1570 return $installResults;
1571 }
1572
1573 /**
1574 * Generate $wgSecretKey. Will warn if we had to use an insecure random source.
1575 *
1576 * @return Status
1577 */
1578 public function generateKeys() {
1579 $keys = array( 'wgSecretKey' => 64 );
1580 if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) {
1581 $keys['wgUpgradeKey'] = 16;
1582 }
1583
1584 return $this->doGenerateKeys( $keys );
1585 }
1586
1587 /**
1588 * Generate a secret value for variables using our CryptRand generator.
1589 * Produce a warning if the random source was insecure.
1590 *
1591 * @param $keys Array
1592 * @return Status
1593 */
1594 protected function doGenerateKeys( $keys ) {
1595 $status = Status::newGood();
1596
1597 $strong = true;
1598 foreach ( $keys as $name => $length ) {
1599 $secretKey = MWCryptRand::generateHex( $length, true );
1600 if ( !MWCryptRand::wasStrong() ) {
1601 $strong = false;
1602 }
1603
1604 $this->setVar( $name, $secretKey );
1605 }
1606
1607 if ( !$strong ) {
1608 $names = array_keys( $keys );
1609 $names = preg_replace( '/^(.*)$/', '\$$1', $names );
1610 global $wgLang;
1611 $status->warning( 'config-insecure-keys', $wgLang->listToText( $names ), count( $names ) );
1612 }
1613
1614 return $status;
1615 }
1616
1617 /**
1618 * Create the first user account, grant it sysop and bureaucrat rights
1619 *
1620 * @return Status
1621 */
1622 protected function createSysop() {
1623 $name = $this->getVar( '_AdminName' );
1624 $user = User::newFromName( $name );
1625
1626 if ( !$user ) {
1627 // We should've validated this earlier anyway!
1628 return Status::newFatal( 'config-admin-error-user', $name );
1629 }
1630
1631 if ( $user->idForName() == 0 ) {
1632 $user->addToDatabase();
1633
1634 try {
1635 $user->setPassword( $this->getVar( '_AdminPassword' ) );
1636 } catch ( PasswordError $pwe ) {
1637 return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() );
1638 }
1639
1640 $user->addGroup( 'sysop' );
1641 $user->addGroup( 'bureaucrat' );
1642 if ( $this->getVar( '_AdminEmail' ) ) {
1643 $user->setEmail( $this->getVar( '_AdminEmail' ) );
1644 }
1645 $user->saveSettings();
1646
1647 // Update user count
1648 $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 );
1649 $ssUpdate->doUpdate();
1650 }
1651 $status = Status::newGood();
1652
1653 if ( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) {
1654 $this->subscribeToMediaWikiAnnounce( $status );
1655 }
1656
1657 return $status;
1658 }
1659
1660 /**
1661 * @param $s Status
1662 */
1663 private function subscribeToMediaWikiAnnounce( Status $s ) {
1664 $params = array(
1665 'email' => $this->getVar( '_AdminEmail' ),
1666 'language' => 'en',
1667 'digest' => 0
1668 );
1669
1670 // Mailman doesn't support as many languages as we do, so check to make
1671 // sure their selected language is available
1672 $myLang = $this->getVar( '_UserLang' );
1673 if ( in_array( $myLang, $this->mediaWikiAnnounceLanguages ) ) {
1674 $myLang = $myLang == 'pt-br' ? 'pt_BR' : $myLang; // rewrite to Mailman's pt_BR
1675 $params['language'] = $myLang;
1676 }
1677
1678 if ( MWHttpRequest::canMakeRequests() ) {
1679 $res = MWHttpRequest::factory( $this->mediaWikiAnnounceUrl,
1680 array( 'method' => 'POST', 'postData' => $params ) )->execute();
1681 if ( !$res->isOK() ) {
1682 $s->warning( 'config-install-subscribe-fail', $res->getMessage() );
1683 }
1684 } else {
1685 $s->warning( 'config-install-subscribe-notpossible' );
1686 }
1687 }
1688
1689 /**
1690 * Insert Main Page with default content.
1691 *
1692 * @param $installer DatabaseInstaller
1693 * @return Status
1694 */
1695 protected function createMainpage( DatabaseInstaller $installer ) {
1696 $status = Status::newGood();
1697 try {
1698 $page = WikiPage::factory( Title::newMainPage() );
1699 $content = new WikitextContent(
1700 wfMessage( 'mainpagetext' )->inContentLanguage()->text() . "\n\n" .
1701 wfMessage( 'mainpagedocfooter' )->inContentLanguage()->text()
1702 );
1703
1704 $page->doEditContent( $content,
1705 '',
1706 EDIT_NEW,
1707 false,
1708 User::newFromName( 'MediaWiki default' )
1709 );
1710 } catch ( MWException $e ) {
1711 //using raw, because $wgShowExceptionDetails can not be set yet
1712 $status->fatal( 'config-install-mainpage-failed', $e->getMessage() );
1713 }
1714
1715 return $status;
1716 }
1717
1718 /**
1719 * Override the necessary bits of the config to run an installation.
1720 */
1721 public static function overrideConfig() {
1722 define( 'MW_NO_SESSION', 1 );
1723
1724 // Don't access the database
1725 $GLOBALS['wgUseDatabaseMessages'] = false;
1726 // Don't cache langconv tables
1727 $GLOBALS['wgLanguageConverterCacheType'] = CACHE_NONE;
1728 // Debug-friendly
1729 $GLOBALS['wgShowExceptionDetails'] = true;
1730 // Don't break forms
1731 $GLOBALS['wgExternalLinkTarget'] = '_blank';
1732
1733 // Extended debugging
1734 $GLOBALS['wgShowSQLErrors'] = true;
1735 $GLOBALS['wgShowDBErrorBacktrace'] = true;
1736
1737 // Allow multiple ob_flush() calls
1738 $GLOBALS['wgDisableOutputCompression'] = true;
1739
1740 // Use a sensible cookie prefix (not my_wiki)
1741 $GLOBALS['wgCookiePrefix'] = 'mw_installer';
1742
1743 // Some of the environment checks make shell requests, remove limits
1744 $GLOBALS['wgMaxShellMemory'] = 0;
1745
1746 // Don't bother embedding images into generated CSS, which is not cached
1747 $GLOBALS['wgResourceLoaderLESSFunctions']['embeddable'] = function ( $frame, $less ) {
1748 return $less->toBool( false );
1749 };
1750 }
1751
1752 /**
1753 * Add an installation step following the given step.
1754 *
1755 * @param array $callback A valid installation callback array, in this form:
1756 * array( 'name' => 'some-unique-name', 'callback' => array( $obj, 'function' ) );
1757 * @param string $findStep the step to find. Omit to put the step at the beginning
1758 */
1759 public function addInstallStep( $callback, $findStep = 'BEGINNING' ) {
1760 $this->extraInstallSteps[$findStep][] = $callback;
1761 }
1762
1763 /**
1764 * Disable the time limit for execution.
1765 * Some long-running pages (Install, Upgrade) will want to do this
1766 */
1767 protected function disableTimeLimit() {
1768 wfSuppressWarnings();
1769 set_time_limit( 0 );
1770 wfRestoreWarnings();
1771 }
1772 }