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