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