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