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