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