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