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