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