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