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