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