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