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