performInstallation() should halt when a fatal status is encountered
[lhc/web/wiklou.git] / includes / installer / Installer.php
1 <?php
2
3 /**
4 * Base installer class
5 * Handles everything that is independent of user interface
6 */
7 abstract class Installer {
8 var $settings, $output;
9
10 /**
11 * MediaWiki configuration globals that will eventually be passed through
12 * to LocalSettings.php. The names only are given here, the defaults
13 * typically come from DefaultSettings.php.
14 */
15 protected $defaultVarNames = array(
16 'wgSitename',
17 'wgPasswordSender',
18 'wgLanguageCode',
19 'wgRightsIcon',
20 'wgRightsText',
21 'wgRightsUrl',
22 'wgMainCacheType',
23 'wgEnableEmail',
24 'wgEnableUserEmail',
25 'wgEnotifUserTalk',
26 'wgEnotifWatchlist',
27 'wgEmailAuthentication',
28 'wgDBtype',
29 'wgDiff3',
30 'wgImageMagickConvertCommand',
31 'IP',
32 'wgScriptPath',
33 'wgScriptExtension',
34 'wgMetaNamespace',
35 'wgDeletedDirectory',
36 'wgEnableUploads',
37 'wgLogo',
38 'wgShellLocale',
39 'wgSecretKey',
40 );
41
42 /**
43 * Variables that are stored alongside globals, and are used for any
44 * configuration of the installation process aside from the MediaWiki
45 * configuration. Map of names to defaults.
46 */
47 protected $internalDefaults = array(
48 '_UserLang' => 'en',
49 '_Environment' => false,
50 '_CompiledDBs' => array(),
51 '_SafeMode' => false,
52 '_RaiseMemory' => false,
53 '_UpgradeDone' => false,
54 '_Caches' => array(),
55 '_InstallUser' => 'root',
56 '_InstallPassword' => '',
57 '_SameAccount' => true,
58 '_CreateDBAccount' => false,
59 '_NamespaceType' => 'site-name',
60 '_AdminName' => null, // will be set later, when the user selects language
61 '_AdminPassword' => '',
62 '_AdminPassword2' => '',
63 '_AdminEmail' => '',
64 '_Subscribe' => false,
65 '_SkipOptional' => 'continue',
66 '_RightsProfile' => 'wiki',
67 '_LicenseCode' => 'none',
68 '_CCDone' => false,
69 '_Extensions' => array(),
70 '_MemCachedServers' => '',
71 );
72
73 /**
74 * Known database types. These correspond to the class names <type>Installer,
75 * and are also MediaWiki database types valid for $wgDBtype.
76 *
77 * To add a new type, create a <type>Installer class and a Database<type>
78 * class, and add a config-type-<type> message to MessagesEn.php.
79 */
80 private $dbTypes = array(
81 'mysql',
82 'postgres',
83 'sqlite',
84 'oracle'
85 );
86
87 /**
88 * Minimum memory size in MB
89 */
90 private $minMemorySize = 50;
91
92 /**
93 * Cached DB installer instances, access using getDBInstaller()
94 */
95 private $dbInstallers = array();
96
97 /**
98 * A list of environment check methods called by doEnvironmentChecks().
99 * These may output warnings using showMessage(), and/or abort the
100 * installation process by returning false.
101 */
102 protected $envChecks = array(
103 'envLatestVersion',
104 'envCheckDB',
105 'envCheckRegisterGlobals',
106 'envCheckMagicQuotes',
107 'envCheckMagicSybase',
108 'envCheckMbstring',
109 'envCheckZE1',
110 'envCheckSafeMode',
111 'envCheckXML',
112 'envCheckPCRE',
113 'envCheckMemory',
114 'envCheckCache',
115 'envCheckDiff3',
116 'envCheckGraphics',
117 'envCheckPath',
118 'envCheckWriteableDir',
119 'envCheckExtension',
120 'envCheckShellLocale',
121 'envCheckUploadsDirectory',
122 );
123
124 /**
125 * Steps for installation.
126 * @TODO Should be protected...
127 */
128 var $installSteps = array(
129 'database',
130 'tables',
131 'interwiki',
132 'secretkey',
133 'sysop',
134 'localsettings',
135 );
136
137 /**
138 * Known object cache types and the functions used to test for their existence
139 */
140 protected $objectCaches = array(
141 'xcache' => 'xcache_get',
142 'apc' => 'apc_fetch',
143 'eaccel' => 'eaccelerator_get',
144 'wincache' => 'wincache_ucache_get'
145 );
146
147 /**
148 * User rights profiles
149 */
150 var $rightsProfiles = array(
151 'wiki' => array(),
152 'no-anon' => array(
153 '*' => array( 'edit' => false )
154 ),
155 'fishbowl' => array(
156 '*' => array(
157 'createaccount' => false,
158 'edit' => false,
159 ),
160 ),
161 'private' => array(
162 '*' => array(
163 'createaccount' => false,
164 'edit' => false,
165 'read' => false,
166 ),
167 ),
168 );
169
170 /**
171 * License types
172 */
173 var $licenses = array(
174 'none' => array(
175 'url' => '',
176 'icon' => '',
177 'text' => ''
178 ),
179 'cc-by-sa' => array(
180 'url' => 'http://creativecommons.org/licenses/by-sa/3.0/',
181 'icon' => '{$wgStylePath}/common/images/cc-by-sa.png',
182 ),
183 'cc-by-nc-sa' => array(
184 'url' => 'http://creativecommons.org/licenses/by-nc-sa/3.0/',
185 'icon' => '{$wgStylePath}/common/images/cc-by-nc-sa.png',
186 ),
187 'pd' => array(
188 'url' => 'http://creativecommons.org/licenses/publicdomain/',
189 'icon' => '{$wgStylePath}/common/images/public-domain.png',
190 ),
191 'gfdl-old' => array(
192 'url' => 'http://www.gnu.org/licenses/old-licenses/fdl-1.2.html',
193 'icon' => '{$wgStylePath}/common/images/gnu-fdl.png',
194 ),
195 'gfdl-current' => array(
196 'url' => 'http://www.gnu.org/copyleft/fdl.html',
197 'icon' => '{$wgStylePath}/common/images/gnu-fdl.png',
198 ),
199 'cc-choose' => array(
200 // details will be filled in by the selector
201 'url' => '',
202 'icon' => '',
203 'text' => '',
204 ),
205 );
206 /**
207 * Cached Title and ParserOptions used by parse()
208 */
209 private $parserTitle, $parserOptions;
210
211 /**
212 * Constructor, always call this from child classes
213 */
214 function __construct() {
215 // Disable the i18n cache and LoadBalancer
216 Language::getLocalisationCache()->disableBackend();
217 LBFactory::disableBackend();
218
219 // Load the installer's i18n file
220 global $wgExtensionMessagesFiles;
221 $wgExtensionMessagesFiles['MediawikiInstaller'] =
222 './includes/installer/Installer.i18n.php';
223
224 global $wgUser;
225 $wgUser = User::newFromId( 0 );
226 // Having a user with id = 0 safeguards us from DB access via User::loadOptions()
227
228 // Set our custom <doclink> hook
229 global $wgHooks;
230 $wgHooks['ParserFirstCallInit'][] = array( $this, 'registerDocLink' );
231
232 $this->settings = $this->internalDefaults;
233 foreach ( $this->defaultVarNames as $var ) {
234 $this->settings[$var] = $GLOBALS[$var];
235 }
236 foreach ( $this->dbTypes as $type ) {
237 $installer = $this->getDBInstaller( $type );
238 if ( !$installer ) {
239 continue;
240 }
241 $defaults = $installer->getGlobalDefaults();
242 foreach ( $installer->getGlobalNames() as $var ) {
243 if ( isset( $defaults[$var] ) ) {
244 $this->settings[$var] = $defaults[$var];
245 } else {
246 $this->settings[$var] = $GLOBALS[$var];
247 }
248 }
249 }
250
251 $this->parserTitle = Title::newFromText( 'Installer' );
252 $this->parserOptions = new ParserOptions;
253 $this->parserOptions->setEditSection( false );
254 }
255
256 /**
257 * UI interface for displaying a short message
258 * The parameters are like parameters to wfMsg().
259 * The messages will be in wikitext format, which will be converted to an
260 * output format such as HTML or text before being sent to the user.
261 */
262 abstract function showMessage( $msg /*, ... */ );
263
264 abstract function showStatusMessage( $status );
265
266 /**
267 * Get a list of known DB types
268 */
269 function getDBTypes() {
270 return $this->dbTypes;
271 }
272
273 /**
274 * Get an instance of InstallerDBType for the specified DB type
275 * @param $type Mixed: DB installer for which is needed, false to use default
276 */
277 function getDBInstaller( $type = false ) {
278 if ( !$type ) {
279 $type = $this->getVar( 'wgDBtype' );
280 }
281 $type = strtolower($type);
282
283 if ( !isset( $this->dbInstallers[$type] ) ) {
284 $class = ucfirst( $type ). 'Installer';
285 if ($class::isCompiled()) {
286 $this->dbInstallers[$type] = new $class( $this );
287 } else {
288 $this->dbInstallers[$type] = false;
289 }
290 }
291 return $this->dbInstallers[$type];
292 }
293
294 /**
295 * Do initial checks of the PHP environment. Set variables according to
296 * the observed environment.
297 *
298 * It's possible that this may be called under the CLI SAPI, not the SAPI
299 * that the wiki will primarily run under. In that case, the subclass should
300 * initialise variables such as wgScriptPath, before calling this function.
301 *
302 * Under the web subclass, it can already be assumed that PHP 5+ is in use
303 * and that sessions are working.
304 */
305 function doEnvironmentChecks() {
306 $this->showMessage( 'config-env-php', phpversion() );
307
308 $good = true;
309 foreach ( $this->envChecks as $check ) {
310 $status = $this->$check();
311 if ( $status === false ) {
312 $good = false;
313 }
314 }
315 $this->setVar( '_Environment', $good );
316 if ( $good ) {
317 $this->showMessage( 'config-env-good' );
318 } else {
319 $this->showMessage( 'config-env-bad' );
320 }
321 return $good;
322 }
323
324 /**
325 * Get an MW configuration variable, or internal installer configuration variable.
326 * The defaults come from $GLOBALS (ultimately DefaultSettings.php).
327 * Installer variables are typically prefixed by an underscore.
328 */
329 function getVar( $name, $default = null ) {
330 if ( !isset( $this->settings[$name] ) ) {
331 return $default;
332 } else {
333 return $this->settings[$name];
334 }
335 }
336
337 /**
338 * Set a MW configuration variable, or internal installer configuration variable.
339 */
340 function setVar( $name, $value ) {
341 $this->settings[$name] = $value;
342 }
343
344 /**
345 * Exports all wg* variables stored by the installer into global scope
346 */
347 function exportVars() {
348 foreach ( $this->settings as $name => $value ) {
349 if ( substr( $name, 0, 2 ) == 'wg' ) {
350 $GLOBALS[$name] = $value;
351 }
352 }
353 }
354
355 /**
356 * Get a fake password for sending back to the user in HTML.
357 * This is a security mechanism to avoid compromise of the password in the
358 * event of session ID compromise.
359 */
360 function getFakePassword( $realPassword ) {
361 return str_repeat( '*', strlen( $realPassword ) );
362 }
363
364 /**
365 * Set a variable which stores a password, except if the new value is a
366 * fake password in which case leave it as it is.
367 */
368 function setPassword( $name, $value ) {
369 if ( !preg_match( '/^\*+$/', $value ) ) {
370 $this->setVar( $name, $value );
371 }
372 }
373
374 /** Check if we're installing the latest version */
375 function envLatestVersion() {
376 global $wgVersion;
377 $latestInfoUrl = 'http://www.mediawiki.org/w/api.php?action=mwreleases&format=json';
378 $latestInfo = Http::get( $latestInfoUrl );
379 if( !$latestInfo ) {
380 $this->showMessage( 'config-env-latest-can-not-check', $latestInfoUrl );
381 return;
382 }
383 $latestInfo = FormatJson::decode($latestInfo);
384 if ($latestInfo === false || !isset( $latestInfo->mwreleases ) ) {
385 # For when the request is successful but there's e.g. some silly man in
386 # the middle firewall blocking us, e.g. one of those annoying airport ones
387 $this->showMessage( 'config-env-latest-data-invalid', $latestInfoUrl );
388 return;
389 }
390 foreach( $latestInfo->mwreleases as $rel ) {
391 if( isset( $rel->current ) )
392 $currentVersion = $rel->version;
393 }
394 if( version_compare( $wgVersion, $currentVersion, '<' ) ) {
395 $this->showMessage( 'config-env-latest-old' );
396 $this->showHelpBox( 'config-env-latest-help', $wgVersion, $currentVersion );
397 } elseif( version_compare( $wgVersion, $currentVersion, '>' ) ) {
398 $this->showMessage( 'config-env-latest-new' );
399 }
400 $this->showMessage( 'config-env-latest-ok' );
401 }
402
403 /** Environment check for DB types */
404 function envCheckDB() {
405 $compiledDBs = array();
406 $goodNames = array();
407 $allNames = array();
408 foreach ( $this->dbTypes as $name ) {
409 $db = $this->getDBInstaller( $name );
410 $readableName = wfMsg( 'config-type-' . $name );
411 if ( $db ) {
412 $compiledDBs[] = $name;
413 $goodNames[] = $readableName;
414 }
415 $allNames[] = $readableName;
416 }
417 $this->setVar( '_CompiledDBs', $compiledDBs );
418
419 global $wgLang;
420 if ( !$compiledDBs ) {
421 $this->showMessage( 'config-no-db' );
422 $this->showHelpBox( 'config-no-db-help', $wgLang->commaList( $allNames ) );
423 return false;
424 }
425 $this->showMessage( 'config-have-db', $wgLang->commaList( $goodNames ) );
426 }
427
428 /** Environment check for register_globals */
429 function envCheckRegisterGlobals() {
430 if( wfIniGetBool( "magic_quotes_runtime" ) ) {
431 $this->showMessage( 'config-register-globals' );
432 }
433 }
434
435 /** Environment check for magic_quotes_runtime */
436 function envCheckMagicQuotes() {
437 if( wfIniGetBool( "magic_quotes_runtime" ) ) {
438 $this->showMessage( 'config-magic-quotes-runtime' );
439 return false;
440 }
441 }
442
443 /** Environment check for magic_quotes_sybase */
444 function envCheckMagicSybase() {
445 if ( wfIniGetBool( 'magic_quotes_sybase' ) ) {
446 $this->showMessage( 'config-magic-quotes-sybase' );
447 return false;
448 }
449 }
450
451 /* Environment check for mbstring.func_overload */
452 function envCheckMbstring() {
453 if ( wfIniGetBool( 'mbstring.func_overload' ) ) {
454 $this->showMessage( 'config-mbstring' );
455 return false;
456 }
457 }
458
459 /** Environment check for zend.ze1_compatibility_mode */
460 function envCheckZE1() {
461 if ( wfIniGetBool( 'zend.ze1_compatibility_mode' ) ) {
462 $this->showMessage( 'config-ze1' );
463 return false;
464 }
465 }
466
467 /** Environment check for safe_mode */
468 function envCheckSafeMode() {
469 if ( wfIniGetBool( 'safe_mode' ) ) {
470 $this->setVar( '_SafeMode', true );
471 $this->showMessage( 'config-safe-mode' );
472 }
473 }
474
475 /** Environment check for the XML module */
476 function envCheckXML() {
477 if ( !function_exists( "utf8_encode" ) ) {
478 $this->showMessage( 'config-xml-bad' );
479 return false;
480 }
481 $this->showMessage( 'config-xml-good' );
482 }
483
484 /** Environment check for the PCRE module */
485 function envCheckPCRE() {
486 if ( !function_exists( 'preg_match' ) ) {
487 $this->showMessage( 'config-pcre' );
488 return false;
489 }
490 }
491
492 /** Environment check for available memory */
493 function envCheckMemory() {
494 $limit = ini_get( 'memory_limit' );
495 if ( !$limit || $limit == -1 ) {
496 $this->showMessage( 'config-memory-none' );
497 return true;
498 }
499 $n = intval( $limit );
500 if( preg_match( '/^([0-9]+)[Mm]$/', trim( $limit ), $m ) ) {
501 $n = intval( $m[1] * (1024*1024) );
502 }
503 if( $n < $this->minMemorySize*1024*1024 ) {
504 $newLimit = "{$this->minMemorySize}M";
505 if( false === ini_set( "memory_limit", $newLimit ) ) {
506 $this->showMessage( 'config-memory-bad', $limit );
507 } else {
508 $this->showMessage( 'config-memory-raised', $limit, $newLimit );
509 $this->setVar( '_RaiseMemory', true );
510 }
511 } else {
512 $this->showMessage( 'config-memory-ok', $limit );
513 }
514 }
515
516 /** Environment check for compiled object cache types */
517 function envCheckCache() {
518 $caches = array();
519 foreach ( $this->objectCaches as $name => $function ) {
520 if ( function_exists( $function ) ) {
521 $caches[$name] = true;
522 $this->showMessage( 'config-' . $name );
523 }
524 }
525 if ( !$caches ) {
526 $this->showMessage( 'config-no-cache' );
527 }
528 $this->setVar( '_Caches', $caches );
529 }
530
531 /** Search for GNU diff3 */
532 function envCheckDiff3() {
533 $paths = array_merge(
534 array(
535 "/usr/bin",
536 "/usr/local/bin",
537 "/opt/csw/bin",
538 "/usr/gnu/bin",
539 "/usr/sfw/bin" ),
540 explode( PATH_SEPARATOR, getenv( "PATH" ) ) );
541 $names = array( "gdiff3", "diff3", "diff3.exe" );
542
543 $versionInfo = array( '$1 --version 2>&1', 'diff3 (GNU diffutils)' );
544 $haveDiff3 = false;
545 foreach ( $paths as $path ) {
546 $exe = $this->locateExecutable( $path, $names, $versionInfo );
547 if ($exe !== false) {
548 $this->setVar( 'wgDiff3', $exe );
549 $haveDiff3 = true;
550 break;
551 }
552 }
553 if ( $haveDiff3 ) {
554 $this->showMessage( 'config-diff3-good', $exe );
555 } else {
556 $this->setVar( 'wgDiff3', false );
557 $this->showMessage( 'config-diff3-bad' );
558 }
559 }
560
561 /**
562 * Search a path for any of the given executable names. Returns the
563 * executable name if found. Also checks the version string returned
564 * by each executable
565 *
566 * @param $path String: path to search
567 * @param $names Array of executable names
568 * @param $versionInfo Boolean false or array with two members:
569 * 0 => Command to run for version check, with $1 for the path
570 * 1 => String to compare the output with
571 *
572 * If $versionInfo is not false, only executables with a version
573 * matching $versionInfo[1] will be returned.
574 */
575 function locateExecutable( $path, $names, $versionInfo = false ) {
576 if (!is_array($names))
577 $names = array($names);
578
579 foreach ($names as $name) {
580 $command = "$path/$name";
581 if ( @file_exists( $command ) ) {
582 if ( !$versionInfo )
583 return $command;
584
585 $file = str_replace( '$1', $command, $versionInfo[0] );
586 # Should maybe be wfShellExec( $file), but runs into a ulimit, see
587 # http://www.mediawiki.org/w/index.php?title=New-installer_issues&diff=prev&oldid=335456
588 if ( strstr( `$file`, $versionInfo[1]) !== false )
589 return $command;
590 }
591 }
592 return false;
593 }
594
595 /** Environment check for ImageMagick and GD */
596 function envCheckGraphics() {
597 $imcheck = array( "/usr/bin", "/opt/csw/bin", "/usr/local/bin", "/sw/bin", "/opt/local/bin" );
598 foreach( $imcheck as $dir ) {
599 $im = "$dir/convert";
600 if( @file_exists( $im ) ) {
601 $this->showMessage( 'config-imagemagick', $im );
602 $this->setVar( 'wgImageMagickConvertCommand', $im );
603 return true;
604 }
605 }
606 if ( function_exists( 'imagejpeg' ) ) {
607 $this->showMessage( 'config-gd' );
608 return true;
609 }
610 $this->showMessage( 'no-scaling' );
611 }
612
613 /** Environment check for setting $IP and $wgScriptPath */
614 function envCheckPath() {
615 $IP = dirname( dirname( dirname( __FILE__ ) ) );
616 $this->setVar( 'IP', $IP );
617 $this->showMessage( 'config-dir', $IP );
618
619 // PHP_SELF isn't available sometimes, such as when PHP is CGI but
620 // cgi.fix_pathinfo is disabled. In that case, fall back to SCRIPT_NAME
621 // to get the path to the current script... hopefully it's reliable. SIGH
622 if ( !empty( $_SERVER['PHP_SELF'] ) ) {
623 $path = $_SERVER['PHP_SELF'];
624 } elseif ( !empty( $_SERVER['SCRIPT_NAME'] ) ) {
625 $path = $_SERVER['SCRIPT_NAME'];
626 } elseif ( $this->getVar( 'wgScriptPath' ) ) {
627 // Some kind soul has set it for us already (e.g. debconf)
628 return true;
629 } else {
630 $this->showMessage( 'config-no-uri' );
631 return false;
632 }
633 $uri = preg_replace( '{^(.*)/config.*$}', '$1', $path );
634 $this->setVar( 'wgScriptPath', $uri );
635 $this->showMessage( 'config-uri', $uri );
636 }
637
638 /** Environment check for writable config/ directory */
639 function envCheckWriteableDir() {
640 $ipDir = $this->getVar( 'IP' );
641 $configDir = $ipDir . '/config';
642 if( !is_writeable( $configDir ) ) {
643 $webserverGroup = self::maybeGetWebserverPrimaryGroup();
644 if ( $webserverGroup !== null ) {
645 $this->showMessage( 'config-dir-not-writable-group', $ipDir, $webserverGroup );
646 } else {
647 $this->showMessage( 'config-dir-not-writable-nogroup', $ipDir, $webserverGroup );
648 }
649 return false;
650 }
651 }
652
653 /** Environment check for setting the preferred PHP file extension */
654 function envCheckExtension() {
655 // FIXME: detect this properly
656 if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) {
657 $ext = 'php5';
658 } else {
659 $ext = 'php';
660 }
661 $this->setVar( 'wgScriptExtension', ".$ext" );
662 $this->showMessage( 'config-file-extension', $ext );
663 }
664
665 function envCheckShellLocale() {
666 # Give up now if we're in safe mode or open_basedir
667 # It's theoretically possible but tricky to work with
668 if ( wfIniGetBool( "safe_mode" ) || ini_get( 'open_basedir' ) || !function_exists( 'exec' ) ) {
669 return true;
670 }
671
672 $os = php_uname( 's' );
673 $supported = array( 'Linux', 'SunOS', 'HP-UX' ); # Tested these
674 if ( !in_array( $os, $supported ) ) {
675 return true;
676 }
677
678 # Get a list of available locales
679 $lines = $ret = false;
680 exec( '/usr/bin/locale -a', $lines, $ret );
681 if ( $ret ) {
682 return true;
683 }
684
685 $lines = wfArrayMap( 'trim', $lines );
686 $candidatesByLocale = array();
687 $candidatesByLang = array();
688 foreach ( $lines as $line ) {
689 if ( $line === '' ) {
690 continue;
691 }
692 if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) {
693 continue;
694 }
695 list( $all, $lang, $territory, $charset, $modifier ) = $m;
696 $candidatesByLocale[$m[0]] = $m;
697 $candidatesByLang[$lang][] = $m;
698 }
699
700 # Try the current value of LANG
701 if ( isset( $candidatesByLocale[ getenv( 'LANG' ) ] ) ) {
702 $this->setVar( 'wgShellLocale', getenv( 'LANG' ) );
703 $this->showMessage( 'config-shell-locale', getenv( 'LANG' ) );
704 return true;
705 }
706
707 # Try the most common ones
708 $commonLocales = array( 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' );
709 foreach ( $commonLocales as $commonLocale ) {
710 if ( isset( $candidatesByLocale[$commonLocale] ) ) {
711 $this->setVar( 'wgShellLocale', $commonLocale );
712 $this->showMessage( 'config-shell-locale', $commonLocale );
713 return true;
714 }
715 }
716
717 # Is there an available locale in the Wiki's language?
718 $wikiLang = $this->getVar( 'wgLanguageCode' );
719 if ( isset( $candidatesByLang[$wikiLang] ) ) {
720 $m = reset( $candidatesByLang[$wikiLang] );
721 $this->setVar( 'wgShellLocale', $m[0] );
722 $this->showMessage( 'config-shell-locale', $m[0] );
723 return true;
724 }
725
726 # Are there any at all?
727 if ( count( $candidatesByLocale ) ) {
728 $m = reset( $candidatesByLocale );
729 $this->setVar( 'wgShellLocale', $m[0] );
730 $this->showMessage( 'config-shell-locale', $m[0] );
731 return true;
732 }
733
734 # Give up
735 return true;
736 }
737
738 function envCheckUploadsDirectory() {
739 global $IP, $wgServer;
740 $dir = $IP . '/images/';
741 $url = $wgServer . $this->getVar( 'wgScriptPath' ) . '/images/';
742 $safe = !$this->dirIsExecutable( $dir, $url );
743 if ( $safe ) {
744 $this->showMessage( 'config-uploads-safe' );
745 } else {
746 $this->showMessage( 'config-uploads-not-safe', $dir );
747 }
748 }
749
750 /**
751 * Checks if scripts located in the given directory can be executed via the given URL
752 */
753 function dirIsExecutable( $dir, $url ) {
754 $scriptTypes = array(
755 'php' => array(
756 "<?php echo 'ex' . 'ec';",
757 "#!/var/env php5\n<?php echo 'ex' . 'ec';",
758 ),
759 );
760 // it would be good to check other popular languages here, but it'll be slow
761
762 wfSuppressWarnings();
763 foreach ( $scriptTypes as $ext => $contents ) {
764 foreach ( $contents as $source ) {
765 $file = 'exectest.' . $ext;
766 if ( !file_put_contents( $dir . $file, $source ) ) {
767 break;
768 }
769 $text = Http::get( $url . $file );
770 unlink( $dir . $file );
771 if ( $text == 'exec' ) {
772 wfRestoreWarnings();
773 return $ext;
774 }
775 }
776 }
777 wfRestoreWarnings();
778 return false;
779 }
780
781 /**
782 * Convert wikitext $text to HTML.
783 *
784 * This is potentially error prone since many parser features require a complete
785 * installed MW database. The solution is to just not use those features when you
786 * write your messages. This appears to work well enough. Basic formatting and
787 * external links work just fine.
788 *
789 * But in case a translator decides to throw in a #ifexist or internal link or
790 * whatever, this function is guarded to catch attempted DB access and to present
791 * some fallback text.
792 *
793 * @param $text String
794 * @param $lineStart Boolean
795 * @return String
796 */
797 function parse( $text, $lineStart = false ) {
798 global $wgParser;
799 try {
800 $out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
801 $html = $out->getText();
802 } catch ( DBAccessError $e ) {
803 $html = '<!--DB access attempted during parse--> ' . htmlspecialchars( $text );
804 if ( !empty( $this->debug ) ) {
805 $html .= "<!--\n" . $e->getTraceAsString() . "\n-->";
806 }
807 }
808 return $html;
809 }
810
811 /**
812 * Register tag hook below
813 */
814 function registerDocLink( &$parser ) {
815 $parser->setHook( 'doclink', array( $this, 'docLink' ) );
816 return true;
817 }
818
819 /**
820 * Extension tag hook for a documentation link
821 */
822 function docLink( $linkText, $attribs, $parser ) {
823 $url = $this->getDocUrl( $attribs['href'] );
824 return '<a href="' . htmlspecialchars( $url ) . '">' .
825 htmlspecialchars( $linkText ) .
826 '</a>';
827 }
828
829 /**
830 * Overridden by WebInstaller to provide lastPage parameters
831 */
832 protected function getDocUrl( $page ) {
833 return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $attribs['href'] );
834 }
835
836 public function findExtensions() {
837 if( $this->getVar( 'IP' ) === null ) {
838 return false;
839 }
840 $exts = array();
841 $dir = $this->getVar( 'IP' ) . '/extensions';
842 $dh = opendir( $dir );
843 while ( ( $file = readdir( $dh ) ) !== false ) {
844 if( file_exists( "$dir/$file/$file.php" ) ) {
845 $exts[$file] = null;
846 }
847 }
848 $this->setVar( '_Extensions', $exts );
849 return $exts;
850 }
851
852 public function getInstallSteps() {
853 if( $this->getVar( '_UpgradeDone' ) ) {
854 $this->installSteps = array( 'localsettings' );
855 }
856 if( count( $this->getVar( '_Extensions' ) ) ) {
857 array_unshift( $this->installSteps, 'extensions' );
858 }
859 return $this->installSteps;
860 }
861
862 /**
863 * Actually perform the installation
864 * @param Array $startCB A callback array for the beginning of each step
865 * @param Array $endCB A callback array for the end of each step
866 * @return Array of Status objects
867 */
868 public function performInstallation( $startCB, $endCB ) {
869 $installResults = array();
870 foreach( $this->getInstallSteps() as $stepObj ) {
871 $step = is_array( $stepObj ) ? $stepObj['name'] : $stepObj;
872 call_user_func_array( $startCB, array( $step ) );
873 $status = null;
874
875 # Call our working function
876 if ( is_array( $step ) ) {
877 # A custom callaback
878 $callback = $stepObj['callback'];
879 $status = call_user_func_array( $callback, array() );
880 } else {
881 # Boring implicitly named callback
882 $func = 'install' . ucfirst( $step );
883 $status = $this->{$func}();
884 }
885 call_user_func_array( $endCB, array( $step, $status ) );
886 $installResults[$step] = $status;
887
888 // If we've hit some sort of fatal, we need to bail. Callback
889 // already had a chance to do output above.
890 if( !$status->isOk() )
891 break;
892 }
893 return $installResults;
894 }
895
896 public function installExtensions() {
897 global $wgHooks, $wgAutoloadClasses;
898 $exts = $this->getVar( '_Extensions' );
899 $path = $this->getVar( 'IP' ) . '/extensions';
900 foreach( $exts as $e ) {
901 require( "$path/$e/$e.php" );
902 }
903 return Status::newGood();
904 }
905
906 public function installDatabase() {
907 $type = $this->getVar( 'wgDBtype' );
908 $installer = $this->getDBInstaller( $type );
909 if(!$installer) {
910 $status = Status::newFatal( "config-no-db", $type );
911 } else {
912 $status = $installer->setupDatabase();
913 }
914 return $status;
915 }
916
917 public function installUser() {
918 $installer = $this->getDBInstaller( $this->getVar( 'wgDBtype' ) );
919 $status = $installer->setupUser();
920 return $status;
921 }
922
923 public function installTables() {
924 $installer = $this->getDBInstaller();
925 $status = $installer->createTables();
926 if( $status->isOK() ) {
927 LBFactory::enableBackend();
928 }
929 return $status;
930 }
931
932 public function installInterwiki() {
933 $installer = $this->getDBInstaller();
934 return $installer->populateInterwikiTable();
935 }
936
937 public function installSecretKey() {
938 if ( wfIsWindows() ) {
939 $file = null;
940 } else {
941 wfSuppressWarnings();
942 $file = fopen( "/dev/urandom", "r" );
943 wfRestoreWarnings();
944 }
945
946 $status = Status::newGood();
947
948 if ( $file ) {
949 $secretKey = bin2hex( fread( $file, 32 ) );
950 fclose( $file );
951 } else {
952 $secretKey = "";
953 for ( $i=0; $i<8; $i++ ) {
954 $secretKey .= dechex(mt_rand(0, 0x7fffffff));
955 }
956 $status->warning( 'config-insecure-secretkey' );
957 }
958 $this->setVar( 'wgSecretKey', $secretKey );
959
960 return $status;
961 }
962
963 public function installSysop() {
964 $name = $this->getVar( '_AdminName' );
965 $user = User::newFromName( $name );
966 if ( !$user ) {
967 // we should've validated this earlier anyway!
968 return Status::newFatal( 'config-admin-error-user', $name );
969 }
970 if ( $user->idForName() == 0 ) {
971 $user->addToDatabase();
972 try {
973 $user->setPassword( $this->getVar( '_AdminPassword' ) );
974 } catch( PasswordError $pwe ) {
975 return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() );
976 }
977 $user->addGroup( 'sysop' );
978 $user->addGroup( 'bureaucrat' );
979 $user->saveSettings();
980 }
981 return Status::newGood();
982 }
983
984 public function installLocalsettings() {
985 $localSettings = new LocalSettingsGenerator( $this );
986 return $localSettings->writeLocalSettings();
987 }
988
989 /**
990 * Determine if LocalSettings exists. If it does, return an appropriate
991 * status for whether we should can upgrade or not
992 * @return Status
993 */
994 function getLocalSettingsStatus() {
995 global $IP;
996
997 $status = Status::newGood();
998
999 wfSuppressWarnings();
1000 $ls = file_exists( "$IP/LocalSettings.php" );
1001 wfRestoreWarnings();
1002
1003 if( $ls ) {
1004 if( $this->getDBInstaller()->needsUpgrade() ) {
1005 $status->warning( 'config-localsettings-upgrade' );
1006 }
1007 else {
1008 $status->fatal( 'config-localsettings-noupgrade' );
1009 }
1010 }
1011 return $status;
1012 }
1013
1014 /**
1015 * On POSIX systems return the primary group of the webserver we're running under.
1016 * On other systems just returns null.
1017 *
1018 * This is used to advice the user that he should chgrp his config/data/images directory as the
1019 * webserver user before he can install.
1020 *
1021 * Public because SqliteInstaller needs it, and doesn't subclass Installer.
1022 *
1023 * @return String
1024 */
1025 public static function maybeGetWebserverPrimaryGroup() {
1026 if ( ! function_exists('posix_getegid') || ! function_exists('posix_getpwuid') ) {
1027 # I don't know this, this isn't UNIX
1028 return null;
1029 }
1030
1031 # posix_getegid() *not* getmygid() because we want the group of the webserver,
1032 # not whoever owns the current script
1033 $gid = posix_getegid();
1034 $getpwuid = posix_getpwuid( $gid );
1035 $group = $getpwuid["name"];
1036
1037 return $group;
1038 }
1039
1040 /**
1041 * Override the necessary bits of the config to run an installation
1042 */
1043 public static function overrideConfig() {
1044 define( 'MW_NO_SESSION', 1 );
1045
1046 // Don't access the database
1047 $GLOBALS['wgUseDatabaseMessages'] = false;
1048 // Debug-friendly
1049 $GLOBALS['wgShowExceptionDetails'] = true;
1050 // Don't break forms
1051 $GLOBALS['wgExternalLinkTarget'] = '_blank';
1052
1053 // Extended debugging. Maybe disable before release?
1054 $GLOBALS['wgShowSQLErrors'] = true;
1055 $GLOBALS['wgShowDBErrorBacktrace'] = true;
1056 }
1057
1058 /**
1059 * Add an installation step following the given step.
1060 * @param $findStep String the step to find. Use NULL to put the step at the beginning.
1061 * @param $callback array
1062 */
1063 function addInstallStepFollowing( $findStep, $callback ) {
1064 $where = 0;
1065 if( $findStep !== null ) $where = array_search( $findStep, $this->installSteps );
1066
1067 array_splice( $this->installSteps, $where, 0, $callback );
1068 }
1069
1070
1071 }