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