Save settings after adding the groups, or the groups don't stick
[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 $this->settings = $this->internalDefaults;
219 foreach ( $this->defaultVarNames as $var ) {
220 $this->settings[$var] = $GLOBALS[$var];
221 }
222 foreach ( $this->dbTypes as $type ) {
223 $installer = $this->getDBInstaller( $type );
224 if ( !$installer->isCompiled() ) {
225 continue;
226 }
227 $defaults = $installer->getGlobalDefaults();
228 foreach ( $installer->getGlobalNames() as $var ) {
229 if ( isset( $defaults[$var] ) ) {
230 $this->settings[$var] = $defaults[$var];
231 } else {
232 $this->settings[$var] = $GLOBALS[$var];
233 }
234 }
235 }
236
237 $this->parserTitle = Title::newFromText( 'Installer' );
238 $this->parserOptions = new ParserOptions;
239 $this->parserOptions->setEditSection( false );
240 }
241
242 /**
243 * UI interface for displaying a short message
244 * The parameters are like parameters to wfMsg().
245 * The messages will be in wikitext format, which will be converted to an
246 * output format such as HTML or text before being sent to the user.
247 */
248 abstract function showMessage( $msg /*, ... */ );
249
250 abstract function showStatusError( $status );
251
252 /**
253 * Get a list of known DB types
254 */
255 function getDBTypes() {
256 return $this->dbTypes;
257 }
258
259 /**
260 * Get an instance of InstallerDBType for the specified DB type
261 * @param $type Mixed: DB installer for which is needed, false to use default
262 */
263 function getDBInstaller( $type = false ) {
264 if ( !$type ) {
265 $type = $this->getVar( 'wgDBtype' );
266 }
267 if ( !isset( $this->dbInstallers[$type] ) ) {
268 $class = ucfirst( $type ). 'Installer';
269 $this->dbInstallers[$type] = new $class( $this );
270 }
271 return $this->dbInstallers[$type];
272 }
273
274 /**
275 * Do initial checks of the PHP environment. Set variables according to
276 * the observed environment.
277 *
278 * It's possible that this may be called under the CLI SAPI, not the SAPI
279 * that the wiki will primarily run under. In that case, the subclass should
280 * initialise variables such as wgScriptPath, before calling this function.
281 *
282 * Under the web subclass, it can already be assumed that PHP 5+ is in use
283 * and that sessions are working.
284 */
285 function doEnvironmentChecks() {
286 $this->showMessage( 'config-env-php', phpversion() );
287
288 $good = true;
289 foreach ( $this->envChecks as $check ) {
290 $status = $this->$check();
291 if ( $status === false ) {
292 $good = false;
293 }
294 }
295 $this->setVar( '_Environment', $good );
296 if ( $good ) {
297 $this->showMessage( 'config-env-good' );
298 } else {
299 $this->showMessage( 'config-env-bad' );
300 }
301 return $good;
302 }
303
304 /**
305 * Get an MW configuration variable, or internal installer configuration variable.
306 * The defaults come from $GLOBALS (ultimately DefaultSettings.php).
307 * Installer variables are typically prefixed by an underscore.
308 */
309 function getVar( $name, $default = null ) {
310 if ( !isset( $this->settings[$name] ) ) {
311 return $default;
312 } else {
313 return $this->settings[$name];
314 }
315 }
316
317 /**
318 * Set a MW configuration variable, or internal installer configuration variable.
319 */
320 function setVar( $name, $value ) {
321 $this->settings[$name] = $value;
322 }
323
324 /**
325 * Exports all wg* variables stored by the installer into global scope
326 */
327 function exportVars() {
328 foreach ( $this->settings as $name => $value ) {
329 if ( substr( $name, 0, 2 ) == 'wg' ) {
330 $GLOBALS[$name] = $value;
331 }
332 }
333 }
334
335 /**
336 * Get a fake password for sending back to the user in HTML.
337 * This is a security mechanism to avoid compromise of the password in the
338 * event of session ID compromise.
339 */
340 function getFakePassword( $realPassword ) {
341 return str_repeat( '*', strlen( $realPassword ) );
342 }
343
344 /**
345 * Set a variable which stores a password, except if the new value is a
346 * fake password in which case leave it as it is.
347 */
348 function setPassword( $name, $value ) {
349 if ( !preg_match( '/^\*+$/', $value ) ) {
350 $this->setVar( $name, $value );
351 }
352 }
353
354 /**
355 * Returns true if dl() can be used
356 */
357 function haveDl() {
358 return function_exists( 'dl' )
359 && is_callable( 'dl' )
360 && wfIniGetBool( 'enable_dl' )
361 && !wfIniGetBool( 'safe_mode' );
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 $haveDl = $this->haveDl();
397 $goodNames = array();
398 $allNames = array();
399 foreach ( $this->dbTypes as $name ) {
400 $db = $this->getDBInstaller( $name );
401 $readableName = wfMsg( 'config-type-' . $name );
402 if ( $db->isCompiled() ) {
403 $compiledDBs[] = $name;
404 $goodNames[] = $readableName;
405 }
406 $allNames[] = $readableName;
407 }
408 $this->setVar( '_CompiledDBs', $compiledDBs );
409
410 global $wgLang;
411 if ( !$compiledDBs ) {
412 $this->showMessage( 'config-no-db' );
413 $this->showHelpBox( 'config-no-db-help', $wgLang->commaList( $allNames ) );
414 return false;
415 }
416 $this->showMessage( 'config-have-db', $wgLang->commaList( $goodNames ) );
417 }
418
419 /** Environment check for register_globals */
420 function envCheckRegisterGlobals() {
421 if( wfIniGetBool( "magic_quotes_runtime" ) ) {
422 $this->showMessage( 'config-register-globals' );
423 }
424 }
425
426 /** Environment check for magic_quotes_runtime */
427 function envCheckMagicQuotes() {
428 if( wfIniGetBool( "magic_quotes_runtime" ) ) {
429 $this->showMessage( 'config-magic-quotes-runtime' );
430 return false;
431 }
432 }
433
434 /** Environment check for magic_quotes_sybase */
435 function envCheckMagicSybase() {
436 if ( wfIniGetBool( 'magic_quotes_sybase' ) ) {
437 $this->showMessage( 'config-magic-quotes-sybase' );
438 return false;
439 }
440 }
441
442 /* Environment check for mbstring.func_overload */
443 function envCheckMbstring() {
444 if ( wfIniGetBool( 'mbstring.func_overload' ) ) {
445 $this->showMessage( 'config-mbstring' );
446 return false;
447 }
448 }
449
450 /** Environment check for zend.ze1_compatibility_mode */
451 function envCheckZE1() {
452 if ( wfIniGetBool( 'zend.ze1_compatibility_mode' ) ) {
453 $this->showMessage( 'config-ze1' );
454 return false;
455 }
456 }
457
458 /** Environment check for safe_mode */
459 function envCheckSafeMode() {
460 if ( wfIniGetBool( 'safe_mode' ) ) {
461 $this->setVar( '_SafeMode', true );
462 $this->showMessage( 'config-safe-mode' );
463 }
464 }
465
466 /** Environment check for the XML module */
467 function envCheckXML() {
468 if ( !function_exists( "utf8_encode" ) ) {
469 $this->showMessage( 'config-xml-bad' );
470 return false;
471 }
472 $this->showMessage( 'config-xml-good' );
473 }
474
475 /** Environment check for the PCRE module */
476 function envCheckPCRE() {
477 if ( !function_exists( 'preg_match' ) ) {
478 $this->showMessage( 'config-pcre' );
479 return false;
480 }
481 }
482
483 /** Environment check for available memory */
484 function envCheckMemory() {
485 $limit = ini_get( 'memory_limit' );
486 if ( !$limit || $limit == -1 ) {
487 $this->showMessage( 'config-memory-none' );
488 return true;
489 }
490 $n = intval( $limit );
491 if( preg_match( '/^([0-9]+)[Mm]$/', trim( $limit ), $m ) ) {
492 $n = intval( $m[1] * (1024*1024) );
493 }
494 if( $n < $this->minMemorySize*1024*1024 ) {
495 $newLimit = "{$this->minMemorySize}M";
496 if( false === ini_set( "memory_limit", $newLimit ) ) {
497 $this->showMessage( 'config-memory-bad', $limit );
498 } else {
499 $this->showMessage( 'config-memory-raised', $limit, $newLimit );
500 $this->setVar( '_RaiseMemory', true );
501 }
502 } else {
503 $this->showMessage( 'config-memory-ok', $limit );
504 }
505 }
506
507 /** Environment check for compiled object cache types */
508 function envCheckCache() {
509 $caches = array();
510 foreach ( $this->objectCaches as $name => $function ) {
511 if ( function_exists( $function ) ) {
512 $caches[$name] = true;
513 $this->showMessage( 'config-' . $name );
514 }
515 }
516 if ( !$caches ) {
517 $this->showMessage( 'config-no-cache' );
518 }
519 $this->setVar( '_Caches', $caches );
520 }
521
522 /** Search for GNU diff3 */
523 function envCheckDiff3() {
524 $paths = array_merge(
525 array(
526 "/usr/bin",
527 "/usr/local/bin",
528 "/opt/csw/bin",
529 "/usr/gnu/bin",
530 "/usr/sfw/bin" ),
531 explode( PATH_SEPARATOR, getenv( "PATH" ) ) );
532 $names = array( "gdiff3", "diff3", "diff3.exe" );
533
534 $versionInfo = array( '$1 --version 2>&1', 'diff3 (GNU diffutils)' );
535 $haveDiff3 = false;
536 foreach ( $paths as $path ) {
537 $exe = $this->locateExecutable( $path, $names, $versionInfo );
538 if ($exe !== false) {
539 $this->setVar( 'wgDiff3', $exe );
540 $haveDiff3 = true;
541 break;
542 }
543 }
544 if ( $haveDiff3 ) {
545 $this->showMessage( 'config-diff3-good', $exe );
546 } else {
547 $this->setVar( 'wgDiff3', false );
548 $this->showMessage( 'config-diff3-bad' );
549 }
550 }
551
552 /**
553 * Search a path for any of the given executable names. Returns the
554 * executable name if found. Also checks the version string returned
555 * by each executable
556 *
557 * @param $path String: path to search
558 * @param $names Array of executable names
559 * @param $versionInfo Boolean false or array with two members:
560 * 0 => Command to run for version check, with $1 for the path
561 * 1 => String to compare the output with
562 *
563 * If $versionInfo is not false, only executables with a version
564 * matching $versionInfo[1] will be returned.
565 */
566 function locateExecutable( $path, $names, $versionInfo = false ) {
567 if (!is_array($names))
568 $names = array($names);
569
570 foreach ($names as $name) {
571 $command = "$path/$name";
572 if ( @file_exists( $command ) ) {
573 if ( !$versionInfo )
574 return $command;
575
576 $file = str_replace( '$1', $command, $versionInfo[0] );
577 if ( strstr( wfShellExec( $file ), $versionInfo[1]) !== false )
578 return $command;
579 }
580 }
581 return false;
582 }
583
584 /** Environment check for ImageMagick and GD */
585 function envCheckGraphics() {
586 $imcheck = array( "/usr/bin", "/opt/csw/bin", "/usr/local/bin", "/sw/bin", "/opt/local/bin" );
587 foreach( $imcheck as $dir ) {
588 $im = "$dir/convert";
589 if( @file_exists( $im ) ) {
590 $this->showMessage( 'config-imagemagick', $im );
591 $this->setVar( 'wgImageMagickConvertCommand', $im );
592 return true;
593 }
594 }
595 if ( function_exists( 'imagejpeg' ) ) {
596 $this->showMessage( 'config-gd' );
597 return true;
598 }
599 $this->showMessage( 'no-scaling' );
600 }
601
602 /** Environment check for setting $IP and $wgScriptPath */
603 function envCheckPath() {
604 $IP = dirname( dirname( dirname( __FILE__ ) ) );
605 $this->setVar( 'IP', $IP );
606 $this->showMessage( 'config-dir', $IP );
607
608 // PHP_SELF isn't available sometimes, such as when PHP is CGI but
609 // cgi.fix_pathinfo is disabled. In that case, fall back to SCRIPT_NAME
610 // to get the path to the current script... hopefully it's reliable. SIGH
611 if ( !empty( $_SERVER['PHP_SELF'] ) ) {
612 $path = $_SERVER['PHP_SELF'];
613 } elseif ( !empty( $_SERVER['SCRIPT_NAME'] ) ) {
614 $path = $_SERVER['SCRIPT_NAME'];
615 } elseif ( $this->getVar( 'wgScriptPath' ) ) {
616 // Some kind soul has set it for us already (e.g. debconf)
617 return true;
618 } else {
619 $this->showMessage( 'config-no-uri' );
620 return false;
621 }
622 $uri = preg_replace( '{^(.*)/config.*$}', '$1', $path );
623 $this->setVar( 'wgScriptPath', $uri );
624 $this->showMessage( 'config-uri', $uri );
625 }
626
627 /** Environment check for writable config/ directory */
628 function envCheckWriteableDir() {
629 $ipDir = $this->getVar( 'IP' );
630 $configDir = $ipDir . '/config';
631 if( !is_writeable( $configDir ) ) {
632 $webserverGroup = self::maybeGetWebserverPrimaryGroup();
633 if ( $webserverGroup !== null ) {
634 $this->showMessage( 'config-dir-not-writable-group', $ipDir, $webserverGroup );
635 } else {
636 $this->showMessage( 'config-dir-not-writable-nogroup', $ipDir, $webserverGroup );
637 }
638 return false;
639 }
640 }
641
642 /** Environment check for setting the preferred PHP file extension */
643 function envCheckExtension() {
644 // FIXME: detect this properly
645 if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) {
646 $ext = 'php5';
647 } else {
648 $ext = 'php';
649 }
650 $this->setVar( 'wgScriptExtension', ".$ext" );
651 $this->showMessage( 'config-file-extension', $ext );
652 }
653
654 function envCheckShellLocale() {
655 # Give up now if we're in safe mode or open_basedir
656 # It's theoretically possible but tricky to work with
657 if ( wfIniGetBool( "safe_mode" ) || ini_get( 'open_basedir' ) || !function_exists( 'exec' ) ) {
658 return true;
659 }
660
661 $os = php_uname( 's' );
662 $supported = array( 'Linux', 'SunOS', 'HP-UX' ); # Tested these
663 if ( !in_array( $os, $supported ) ) {
664 return true;
665 }
666
667 # Get a list of available locales
668 $lines = $ret = false;
669 exec( '/usr/bin/locale -a', $lines, $ret );
670 if ( $ret ) {
671 return true;
672 }
673
674 $lines = wfArrayMap( 'trim', $lines );
675 $candidatesByLocale = array();
676 $candidatesByLang = array();
677 foreach ( $lines as $line ) {
678 if ( $line === '' ) {
679 continue;
680 }
681 if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) {
682 continue;
683 }
684 list( $all, $lang, $territory, $charset, $modifier ) = $m;
685 $candidatesByLocale[$m[0]] = $m;
686 $candidatesByLang[$lang][] = $m;
687 }
688
689 # Try the current value of LANG
690 if ( isset( $candidatesByLocale[ getenv( 'LANG' ) ] ) ) {
691 $this->setVar( 'wgShellLocale', getenv( 'LANG' ) );
692 $this->showMessage( 'config-shell-locale', getenv( 'LANG' ) );
693 return true;
694 }
695
696 # Try the most common ones
697 $commonLocales = array( 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' );
698 foreach ( $commonLocales as $commonLocale ) {
699 if ( isset( $candidatesByLocale[$commonLocale] ) ) {
700 $this->setVar( 'wgShellLocale', $commonLocale );
701 $this->showMessage( 'config-shell-locale', $commonLocale );
702 return true;
703 }
704 }
705
706 # Is there an available locale in the Wiki's language?
707 $wikiLang = $this->getVar( 'wgLanguageCode' );
708 if ( isset( $candidatesByLang[$wikiLang] ) ) {
709 $m = reset( $candidatesByLang[$wikiLang] );
710 $this->setVar( 'wgShellLocale', $m[0] );
711 $this->showMessage( 'config-shell-locale', $m[0] );
712 return true;
713 }
714
715 # Are there any at all?
716 if ( count( $candidatesByLocale ) ) {
717 $m = reset( $candidatesByLocale );
718 $this->setVar( 'wgShellLocale', $m[0] );
719 $this->showMessage( 'config-shell-locale', $m[0] );
720 return true;
721 }
722
723 # Give up
724 return true;
725 }
726
727 function envCheckUploadsDirectory() {
728 global $IP, $wgServer;
729 $dir = $IP . '/images/';
730 $url = $wgServer . $this->getVar( 'wgScriptPath' ) . '/images/';
731 $safe = !$this->dirIsExecutable( $dir, $url );
732 if ( $safe ) {
733 $this->showMessage( 'config-uploads-safe' );
734 } else {
735 $this->showMessage( 'config-uploads-not-safe', $dir );
736 }
737 }
738
739 /**
740 * Checks if scripts located in the given directory can be executed via the given URL
741 */
742 function dirIsExecutable( $dir, $url ) {
743 $scriptTypes = array(
744 'php' => array(
745 "<?php echo 'ex' . 'ec';",
746 "#!/var/env php5\n<?php echo 'ex' . 'ec';",
747 ),
748 );
749 // it would be good to check other popular languages here, but it'll be slow
750
751 wfSuppressWarnings();
752 foreach ( $scriptTypes as $ext => $contents ) {
753 foreach ( $contents as $source ) {
754 $file = 'exectest.' . $ext;
755 if ( !file_put_contents( $dir . $file, $source ) ) {
756 break;
757 }
758 $text = Http::get( $url . $file );
759 unlink( $dir . $file );
760 if ( $text == 'exec' ) {
761 wfRestoreWarnings();
762 return $ext;
763 }
764 }
765 }
766 wfRestoreWarnings();
767 return false;
768 }
769
770 /**
771 * Convert wikitext $text to HTML.
772 *
773 * This is potentially error prone since many parser features require a complete
774 * installed MW database. The solution is to just not use those features when you
775 * write your messages. This appears to work well enough. Basic formatting and
776 * external links work just fine.
777 *
778 * But in case a translator decides to throw in a #ifexist or internal link or
779 * whatever, this function is guarded to catch attempted DB access and to present
780 * some fallback text.
781 *
782 * @param $text String
783 * @param $lineStart Boolean
784 * @return String
785 */
786 function parse( $text, $lineStart = false ) {
787 global $wgParser;
788 try {
789 $out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
790 $html = $out->getText();
791 } catch ( InstallerDBAccessError $e ) {
792 $html = '<!--DB access attempted during parse--> ' . htmlspecialchars( $text );
793 if ( !empty( $this->debug ) ) {
794 $html .= "<!--\n" . $e->getTraceAsString() . "\n-->";
795 }
796 }
797 return $html;
798 }
799
800 /**
801 * Extension tag hook for a documentation link
802 */
803 function docLink( $linkText, $attribs, $parser ) {
804 $url = $this->getDocUrl( $attribs['href'] );
805 return '<a href="' . htmlspecialchars( $url ) . '">' .
806 htmlspecialchars( $linkText ) .
807 '</a>';
808 }
809
810 /**
811 * Overridden by WebInstaller to provide lastPage parameters
812 */
813 protected function getDocUrl( $page ) {
814 return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $attribs['href'] );
815 }
816
817 public function findExtensions() {
818 if( $this->getVar( 'IP' ) === null ) {
819 return false;
820 }
821 $exts = array();
822 $dir = $this->getVar( 'IP' ) . '/extensions';
823 $dh = opendir( $dir );
824 while ( ( $file = readdir( $dh ) ) !== false ) {
825 if( file_exists( "$dir/$file/$file.php" ) ) {
826 $exts[] = $file;
827 }
828 }
829 $this->setVar( '_Extensions', $exts );
830 return $exts;
831 }
832
833 public function getInstallSteps() {
834 if( $this->getVar( '_UpgradeDone' ) ) {
835 $this->installSteps = array( 'localsettings' );
836 }
837 if( count( $this->getVar( '_Extensions' ) ) ) {
838 array_unshift( $this->installSteps, 'extensions' );
839 }
840 return $this->installSteps;
841 }
842
843 public function installExtensions() {
844 global $wgHooks, $wgAutoloadClasses;
845 $exts = $this->getVar( '_Extensions' );
846 $path = $this->getVar( 'IP' ) . '/extensions';
847 foreach( $exts as $e ) {
848 require( "$path/$e/$e.php" );
849 }
850 return Status::newGood();
851 }
852
853 public function installDatabase() {
854 $installer = $this->getDBInstaller( $this->getVar( 'wgDBtype' ) );
855 $status = $installer->setupDatabase();
856 return $status;
857 }
858
859 public function installTables() {
860 $installer = $this->getDBInstaller();
861 $status = $installer->createTables();
862 if( $status->isGood() ) {
863 LBFactory::enableBackend();
864 }
865 return $status;
866 }
867
868 public function installInterwiki() {
869 $installer = $this->getDBInstaller();
870 return $installer->populateInterwikiTable();
871 }
872
873 public function installSecretKey() {
874 $file = wfIsWindows() ? null : @fopen( "/dev/urandom", "r" );
875 if ( $file ) {
876 $secretKey = bin2hex( fread( $file, 32 ) );
877 fclose( $file );
878 } else {
879 $secretKey = "";
880 for ( $i=0; $i<8; $i++ ) {
881 $secretKey .= dechex(mt_rand(0, 0x7fffffff));
882 }
883 $this->output->addWarningMsg( 'config-insecure-secretkey' );
884 }
885 $this->setVar( 'wgSecretKey', $secretKey );
886 return Status::newGood();
887 }
888
889 public function installSysop() {
890 $name = $this->getVar( '_AdminName' );
891 $user = User::newFromName( $name );
892 if ( !$user ) {
893 // we should've validated this earlier anyway!
894 return Status::newFatal( 'config-admin-error-user', $name );
895 }
896 if ( $user->idForName() == 0 ) {
897 $user->addToDatabase();
898 try {
899 $user->setPassword( $this->getVar( '_AdminPassword' ) );
900 } catch( PasswordError $pwe ) {
901 return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() );
902 }
903 $user->addGroup( 'sysop' );
904 $user->addGroup( 'bureaucrat' );
905 $user->saveSettings();
906 }
907 return Status::newGood();
908 }
909
910 public function installLocalsettings() {
911 $localSettings = new LocalSettingsGenerator( $this );
912 $ok = $localSettings->writeLocalSettings();
913
914 # TODO: Make writeLocalSettings() itself not warn, but instead return
915 # a Status object to us to pass along.
916 if ( $ok ) {
917 return Status::newGood();
918 } else {
919 return Status::newFatal();
920 }
921 }
922
923 /*
924 * On POSIX systems return the primary group of the webserver we're running under.
925 * On other systems just returns null.
926 *
927 * This is used to advice the user that he should chgrp his config/data/images directory as the
928 * webserver user before he can install.
929 *
930 * Public because SqliteInstaller needs it, and doesn't subclass Installer.
931 *
932 * @return String
933 */
934 public static function maybeGetWebserverPrimaryGroup() {
935 if ( ! function_exists('posix_getegid') || ! function_exists('posix_getpwuid') ) {
936 # I don't know this, this isn't UNIX
937 return null;
938 }
939
940 # posix_getegid() *not* getmygid() because we want the group of the webserver,
941 # not whoever owns the current script
942 $gid = posix_getegid();
943 $getpwuid = posix_getpwuid( $gid );
944 $group = $getpwuid["name"];
945
946 return $group;
947 }
948 }