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