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