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