Include short descriptions for extensions bundled in the release
[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 * Load the extension credits for i18n strings. Very hacky for
1412 * now, but I expect it only be used for 1.22.0 at the most.
1413 */
1414 public function getExtensionInfo( $file ) {
1415 global $wgExtensionCredits, $wgVersion, $wgResourceModules;
1416
1417 $wgVersion = "1.22";
1418 $wgResourceModules = array();
1419 require_once $file ;
1420 $e = array_values( $wgExtensionCredits );
1421 if( $e ) {
1422 $ext = array_values( $e[0] );
1423 $wgExtensionCredits = array();
1424 return $ext[0];
1425 }
1426 }
1427
1428 /**
1429 * Finds extensions that follow the format /extensions/Name/Name.php,
1430 * and returns an array containing the value for 'Name' for each found extension.
1431 *
1432 * @return array
1433 */
1434 public function findExtensions() {
1435 if ( $this->getVar( 'IP' ) === null ) {
1436 return array();
1437 }
1438
1439 $extDir = $this->getVar( 'IP' ) . '/extensions';
1440 if ( !is_readable( $extDir ) || !is_dir( $extDir ) ) {
1441 return array();
1442 }
1443
1444 $dh = opendir( $extDir );
1445 $exts = array();
1446 while ( ( $file = readdir( $dh ) ) !== false ) {
1447 if ( !is_dir( "$extDir/$file" ) ) {
1448 continue;
1449 }
1450
1451 $extFile = "$extDir/$file/$file.php";
1452 $extI18NFile = "$extDir/$file/$file.i18n.php";
1453 if ( file_exists( $extFile ) ) {
1454 if ( $info = $this->getExtensionInfo( $extFile ) ) {
1455 $exts[$info['name']] = $info;
1456
1457 if ( file_exists( $extI18NFile ) ) {
1458 global $wgExtensionMessagesFiles;
1459 $wgExtensionMessagesFiles[$file] = $extI18NFile;
1460 }
1461 }
1462 }
1463 }
1464 closedir( $dh );
1465 uksort( $exts, 'strnatcasecmp' );
1466 return $exts;
1467 }
1468
1469 /**
1470 * Installs the auto-detected extensions.
1471 *
1472 * @return Status
1473 */
1474 protected function includeExtensions() {
1475 global $IP;
1476 $exts = $this->getVar( '_Extensions' );
1477 $IP = $this->getVar( 'IP' );
1478
1479 /**
1480 * We need to include DefaultSettings before including extensions to avoid
1481 * warnings about unset variables. However, the only thing we really
1482 * want here is $wgHooks['LoadExtensionSchemaUpdates']. This won't work
1483 * if the extension has hidden hook registration in $wgExtensionFunctions,
1484 * but we're not opening that can of worms
1485 * @see https://bugzilla.wikimedia.org/show_bug.cgi?id=26857
1486 */
1487 global $wgAutoloadClasses;
1488 $wgAutoloadClasses = array();
1489
1490 require "$IP/includes/DefaultSettings.php";
1491
1492 $extensions = $this->findExtensions();
1493 foreach ( $exts as $e ) {
1494 require_once $extensions[$e]['path'];
1495 }
1496
1497 $hooksWeWant = isset( $wgHooks['LoadExtensionSchemaUpdates'] ) ?
1498 $wgHooks['LoadExtensionSchemaUpdates'] : array();
1499
1500 // Unset everyone else's hooks. Lord knows what someone might be doing
1501 // in ParserFirstCallInit (see bug 27171)
1502 $GLOBALS['wgHooks'] = array( 'LoadExtensionSchemaUpdates' => $hooksWeWant );
1503
1504 return Status::newGood();
1505 }
1506
1507 /**
1508 * Get an array of install steps. Should always be in the format of
1509 * array(
1510 * 'name' => 'someuniquename',
1511 * 'callback' => array( $obj, 'method' ),
1512 * )
1513 * There must be a config-install-$name message defined per step, which will
1514 * be shown on install.
1515 *
1516 * @param $installer DatabaseInstaller so we can make callbacks
1517 * @return array
1518 */
1519 protected function getInstallSteps( DatabaseInstaller $installer ) {
1520 $coreInstallSteps = array(
1521 array( 'name' => 'database', 'callback' => array( $installer, 'setupDatabase' ) ),
1522 array( 'name' => 'tables', 'callback' => array( $installer, 'createTables' ) ),
1523 array( 'name' => 'interwiki', 'callback' => array( $installer, 'populateInterwikiTable' ) ),
1524 array( 'name' => 'stats', 'callback' => array( $this, 'populateSiteStats' ) ),
1525 array( 'name' => 'keys', 'callback' => array( $this, 'generateKeys' ) ),
1526 array( 'name' => 'sysop', 'callback' => array( $this, 'createSysop' ) ),
1527 array( 'name' => 'mainpage', 'callback' => array( $this, 'createMainpage' ) ),
1528 );
1529
1530 // Build the array of install steps starting from the core install list,
1531 // then adding any callbacks that wanted to attach after a given step
1532 foreach ( $coreInstallSteps as $step ) {
1533 $this->installSteps[] = $step;
1534 if ( isset( $this->extraInstallSteps[$step['name']] ) ) {
1535 $this->installSteps = array_merge(
1536 $this->installSteps,
1537 $this->extraInstallSteps[$step['name']]
1538 );
1539 }
1540 }
1541
1542 // Prepend any steps that want to be at the beginning
1543 if ( isset( $this->extraInstallSteps['BEGINNING'] ) ) {
1544 $this->installSteps = array_merge(
1545 $this->extraInstallSteps['BEGINNING'],
1546 $this->installSteps
1547 );
1548 }
1549
1550 // Extensions should always go first, chance to tie into hooks and such
1551 if ( count( $this->getVar( '_Extensions' ) ) ) {
1552 array_unshift( $this->installSteps,
1553 array( 'name' => 'extensions', 'callback' => array( $this, 'includeExtensions' ) )
1554 );
1555 $this->installSteps[] = array(
1556 'name' => 'extension-tables',
1557 'callback' => array( $installer, 'createExtensionTables' )
1558 );
1559 }
1560
1561 return $this->installSteps;
1562 }
1563
1564 /**
1565 * Actually perform the installation.
1566 *
1567 * @param array $startCB A callback array for the beginning of each step
1568 * @param array $endCB A callback array for the end of each step
1569 *
1570 * @return Array of Status objects
1571 */
1572 public function performInstallation( $startCB, $endCB ) {
1573 $installResults = array();
1574 $installer = $this->getDBInstaller();
1575 $installer->preInstall();
1576 $steps = $this->getInstallSteps( $installer );
1577 foreach ( $steps as $stepObj ) {
1578 $name = $stepObj['name'];
1579 call_user_func_array( $startCB, array( $name ) );
1580
1581 // Perform the callback step
1582 $status = call_user_func( $stepObj['callback'], $installer );
1583
1584 // Output and save the results
1585 call_user_func( $endCB, $name, $status );
1586 $installResults[$name] = $status;
1587
1588 // If we've hit some sort of fatal, we need to bail.
1589 // Callback already had a chance to do output above.
1590 if ( !$status->isOk() ) {
1591 break;
1592 }
1593 }
1594 if ( $status->isOk() ) {
1595 $this->setVar( '_InstallDone', true );
1596 }
1597
1598 return $installResults;
1599 }
1600
1601 /**
1602 * Generate $wgSecretKey. Will warn if we had to use an insecure random source.
1603 *
1604 * @return Status
1605 */
1606 public function generateKeys() {
1607 $keys = array( 'wgSecretKey' => 64 );
1608 if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) {
1609 $keys['wgUpgradeKey'] = 16;
1610 }
1611
1612 return $this->doGenerateKeys( $keys );
1613 }
1614
1615 /**
1616 * Generate a secret value for variables using our CryptRand generator.
1617 * Produce a warning if the random source was insecure.
1618 *
1619 * @param $keys Array
1620 * @return Status
1621 */
1622 protected function doGenerateKeys( $keys ) {
1623 $status = Status::newGood();
1624
1625 $strong = true;
1626 foreach ( $keys as $name => $length ) {
1627 $secretKey = MWCryptRand::generateHex( $length, true );
1628 if ( !MWCryptRand::wasStrong() ) {
1629 $strong = false;
1630 }
1631
1632 $this->setVar( $name, $secretKey );
1633 }
1634
1635 if ( !$strong ) {
1636 $names = array_keys( $keys );
1637 $names = preg_replace( '/^(.*)$/', '\$$1', $names );
1638 global $wgLang;
1639 $status->warning( 'config-insecure-keys', $wgLang->listToText( $names ), count( $names ) );
1640 }
1641
1642 return $status;
1643 }
1644
1645 /**
1646 * Create the first user account, grant it sysop and bureaucrat rights
1647 *
1648 * @return Status
1649 */
1650 protected function createSysop() {
1651 $name = $this->getVar( '_AdminName' );
1652 $user = User::newFromName( $name );
1653
1654 if ( !$user ) {
1655 // We should've validated this earlier anyway!
1656 return Status::newFatal( 'config-admin-error-user', $name );
1657 }
1658
1659 if ( $user->idForName() == 0 ) {
1660 $user->addToDatabase();
1661
1662 try {
1663 $user->setPassword( $this->getVar( '_AdminPassword' ) );
1664 } catch ( PasswordError $pwe ) {
1665 return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() );
1666 }
1667
1668 $user->addGroup( 'sysop' );
1669 $user->addGroup( 'bureaucrat' );
1670 if ( $this->getVar( '_AdminEmail' ) ) {
1671 $user->setEmail( $this->getVar( '_AdminEmail' ) );
1672 }
1673 $user->saveSettings();
1674
1675 // Update user count
1676 $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 );
1677 $ssUpdate->doUpdate();
1678 }
1679 $status = Status::newGood();
1680
1681 if ( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) {
1682 $this->subscribeToMediaWikiAnnounce( $status );
1683 }
1684
1685 return $status;
1686 }
1687
1688 /**
1689 * @param $s Status
1690 */
1691 private function subscribeToMediaWikiAnnounce( Status $s ) {
1692 $params = array(
1693 'email' => $this->getVar( '_AdminEmail' ),
1694 'language' => 'en',
1695 'digest' => 0
1696 );
1697
1698 // Mailman doesn't support as many languages as we do, so check to make
1699 // sure their selected language is available
1700 $myLang = $this->getVar( '_UserLang' );
1701 if ( in_array( $myLang, $this->mediaWikiAnnounceLanguages ) ) {
1702 $myLang = $myLang == 'pt-br' ? 'pt_BR' : $myLang; // rewrite to Mailman's pt_BR
1703 $params['language'] = $myLang;
1704 }
1705
1706 if ( MWHttpRequest::canMakeRequests() ) {
1707 $res = MWHttpRequest::factory( $this->mediaWikiAnnounceUrl,
1708 array( 'method' => 'POST', 'postData' => $params ) )->execute();
1709 if ( !$res->isOK() ) {
1710 $s->warning( 'config-install-subscribe-fail', $res->getMessage() );
1711 }
1712 } else {
1713 $s->warning( 'config-install-subscribe-notpossible' );
1714 }
1715 }
1716
1717 /**
1718 * Insert Main Page with default content.
1719 *
1720 * @param $installer DatabaseInstaller
1721 * @return Status
1722 */
1723 protected function createMainpage( DatabaseInstaller $installer ) {
1724 $status = Status::newGood();
1725 try {
1726 $page = WikiPage::factory( Title::newMainPage() );
1727 $content = new WikitextContent(
1728 wfMessage( 'mainpagetext' )->inContentLanguage()->text() . "\n\n" .
1729 wfMessage( 'mainpagedocfooter' )->inContentLanguage()->text()
1730 );
1731
1732 $page->doEditContent( $content,
1733 '',
1734 EDIT_NEW,
1735 false,
1736 User::newFromName( 'MediaWiki default' )
1737 );
1738 } catch ( MWException $e ) {
1739 //using raw, because $wgShowExceptionDetails can not be set yet
1740 $status->fatal( 'config-install-mainpage-failed', $e->getMessage() );
1741 }
1742
1743 return $status;
1744 }
1745
1746 /**
1747 * Override the necessary bits of the config to run an installation.
1748 */
1749 public static function overrideConfig() {
1750 define( 'MW_NO_SESSION', 1 );
1751
1752 // Don't access the database
1753 $GLOBALS['wgUseDatabaseMessages'] = false;
1754 // Don't cache langconv tables
1755 $GLOBALS['wgLanguageConverterCacheType'] = CACHE_NONE;
1756 // Debug-friendly
1757 $GLOBALS['wgShowExceptionDetails'] = true;
1758 // Don't break forms
1759 $GLOBALS['wgExternalLinkTarget'] = '_blank';
1760
1761 // Extended debugging
1762 $GLOBALS['wgShowSQLErrors'] = true;
1763 $GLOBALS['wgShowDBErrorBacktrace'] = true;
1764
1765 // Allow multiple ob_flush() calls
1766 $GLOBALS['wgDisableOutputCompression'] = true;
1767
1768 // Use a sensible cookie prefix (not my_wiki)
1769 $GLOBALS['wgCookiePrefix'] = 'mw_installer';
1770
1771 // Some of the environment checks make shell requests, remove limits
1772 $GLOBALS['wgMaxShellMemory'] = 0;
1773
1774 // Don't bother embedding images into generated CSS, which is not cached
1775 $GLOBALS['wgResourceLoaderLESSFunctions']['embeddable'] = function ( $frame, $less ) {
1776 return $less->toBool( false );
1777 };
1778 }
1779
1780 /**
1781 * Add an installation step following the given step.
1782 *
1783 * @param array $callback A valid installation callback array, in this form:
1784 * array( 'name' => 'some-unique-name', 'callback' => array( $obj, 'function' ) );
1785 * @param string $findStep the step to find. Omit to put the step at the beginning
1786 */
1787 public function addInstallStep( $callback, $findStep = 'BEGINNING' ) {
1788 $this->extraInstallSteps[$findStep][] = $callback;
1789 }
1790
1791 /**
1792 * Disable the time limit for execution.
1793 * Some long-running pages (Install, Upgrade) will want to do this
1794 */
1795 protected function disableTimeLimit() {
1796 wfSuppressWarnings();
1797 set_time_limit( 0 );
1798 wfRestoreWarnings();
1799 }
1800 }