new-installer: GRANT permissions to our new non-root user by sourcing users.sql
[lhc/web/wiklou.git] / includes / installer / Installer.php
1 <?php
2
3 /**
4 * Base installer class
5 * Handles everything that is independent of user interface
6 */
7 abstract class Installer {
8 var $settings, $output;
9
10 /**
11 * MediaWiki configuration globals that will eventually be passed through
12 * to LocalSettings.php. The names only are given here, the defaults
13 * typically come from DefaultSettings.php.
14 * @protected
15 */
16 var $defaultVarNames = array(
17 'wgSitename',
18 'wgPasswordSender',
19 'wgLanguageCode',
20 'wgRightsIcon',
21 'wgRightsText',
22 'wgRightsUrl',
23 'wgMainCacheType',
24 'wgEnableEmail',
25 'wgEnableUserEmail',
26 'wgEnotifUserTalk',
27 'wgEnotifWatchlist',
28 'wgEmailAuthentication',
29 'wgDBtype',
30 'wgDiff3',
31 'wgImageMagickConvertCommand',
32 'IP',
33 'wgScriptPath',
34 'wgScriptExtension',
35 'wgMetaNamespace',
36 // 'wgDeletedDirectory',
37 'wgEnableUploads',
38 'wgLogo',
39 'wgShellLocale',
40 'wgSecretKey',
41 );
42
43 /**
44 * Variables that are stored alongside globals, and are used for any
45 * configuration of the installation process aside from the MediaWiki
46 * configuration. Map of names to defaults.
47 * @protected
48 */
49 var $internalDefaults = array(
50 '_UserLang' => 'en',
51 '_Environment' => false,
52 '_CompiledDBs' => array(),
53 '_SafeMode' => false,
54 '_RaiseMemory' => false,
55 '_UpgradeDone' => false,
56 '_Caches' => array(),
57 '_InstallUser' => 'root',
58 '_InstallPassword' => '',
59 '_SameAccount' => true,
60 '_CreateDBAccount' => false,
61 '_NamespaceType' => 'site-name',
62 '_AdminName' => null, // will be set later, when the user selects language
63 '_AdminPassword' => '',
64 '_AdminPassword2' => '',
65 '_AdminEmail' => '',
66 '_Subscribe' => false,
67 '_SkipOptional' => 'continue',
68 '_RightsProfile' => 'wiki',
69 '_LicenseCode' => 'none',
70 '_CCDone' => false,
71 '_Extensions' => array(),
72 '_MemCachedServers' => '',
73 );
74
75 /**
76 * Known database types. These correspond to the class names <type>Installer,
77 * and are also MediaWiki database types valid for $wgDBtype.
78 *
79 * To add a new type, create a <type>Installer class and a Database<type>
80 * class, and add a config-type-<type> message to MessagesEn.php.
81 * @private
82 */
83 var $dbTypes = array(
84 'mysql',
85 'postgres',
86 'sqlite',
87 'oracle'
88 );
89
90 /**
91 * Minimum memory size in MB
92 */
93 private $minMemorySize = 50;
94
95 /**
96 * Cached DB installer instances, access using getDBInstaller()
97 * @private
98 */
99 var $dbInstallers = array();
100
101 /**
102 * A list of environment check methods called by doEnvironmentChecks().
103 * These may output warnings using showMessage(), and/or abort the
104 * installation process by returning false.
105 * @protected
106 */
107 var $envChecks = array(
108 'envLatestVersion',
109 'envCheckDB',
110 'envCheckRegisterGlobals',
111 'envCheckMagicQuotes',
112 'envCheckMagicSybase',
113 'envCheckMbstring',
114 'envCheckZE1',
115 'envCheckSafeMode',
116 'envCheckXML',
117 'envCheckPCRE',
118 'envCheckMemory',
119 'envCheckCache',
120 'envCheckDiff3',
121 'envCheckGraphics',
122 'envCheckPath',
123 'envCheckWriteableDir',
124 'envCheckExtension',
125 'envCheckShellLocale',
126 'envCheckUploadsDirectory',
127 );
128
129 var $installSteps = array(
130 'database',
131 'user',
132 'tables',
133 'interwiki',
134 'secretkey',
135 'sysop',
136 'localsettings',
137 );
138
139 /**
140 * Known object cache types and the functions used to test for their existence
141 * @protected
142 */
143 var $objectCaches = array(
144 'xcache' => 'xcache_get',
145 'apc' => 'apc_fetch',
146 'eaccel' => 'eaccelerator_get',
147 'wincache' => 'wincache_ucache_get'
148 );
149
150 /**
151 * User rights profiles
152 */
153 var $rightsProfiles = array(
154 'wiki' => array(),
155 'no-anon' => array(
156 '*' => array( 'edit' => false )
157 ),
158 'fishbowl' => array(
159 '*' => array(
160 'createaccount' => false,
161 'edit' => false,
162 ),
163 ),
164 'private' => array(
165 '*' => array(
166 'createaccount' => false,
167 'edit' => false,
168 'read' => false,
169 ),
170 ),
171 );
172
173 /**
174 * License types
175 */
176 var $licenses = array(
177 'none' => array(
178 'url' => '',
179 'icon' => '',
180 'text' => ''
181 ),
182 'cc-by-sa' => array(
183 'url' => 'http://creativecommons.org/licenses/by-sa/3.0/',
184 'icon' => '{$wgStylePath}/common/images/cc-by-sa.png',
185 ),
186 'cc-by-nc-sa' => array(
187 'url' => 'http://creativecommons.org/licenses/by-nc-sa/3.0/',
188 'icon' => '{$wgStylePath}/common/images/cc-by-nc-sa.png',
189 ),
190 'pd' => array(
191 'url' => 'http://creativecommons.org/licenses/publicdomain/',
192 'icon' => '{$wgStylePath}/common/images/public-domain.png',
193 ),
194 'gfdl-old' => array(
195 'url' => 'http://www.gnu.org/licenses/old-licenses/fdl-1.2.html',
196 'icon' => '{$wgStylePath}/common/images/gnu-fdl.png',
197 ),
198 'gfdl-current' => array(
199 'url' => 'http://www.gnu.org/copyleft/fdl.html',
200 'icon' => '{$wgStylePath}/common/images/gnu-fdl.png',
201 ),
202 'cc-choose' => array(
203 // details will be filled in by the selector
204 'url' => '',
205 'icon' => '',
206 'text' => '',
207 ),
208 );
209 /**
210 * Cached Title and ParserOptions used by parse()
211 * @private
212 */
213 var $parserTitle, $parserOptions;
214
215 /**
216 * Constructor, always call this from child classes
217 */
218 function __construct() {
219 // Disable the i18n cache and LoadBalancer
220 Language::getLocalisationCache()->disableBackend();
221 LBFactory::disableBackend();
222
223 // Load the installer's i18n file
224 global $wgExtensionMessagesFiles;
225 $wgExtensionMessagesFiles['MediawikiInstaller'] =
226 './includes/installer/Installer.i18n.php';
227
228 global $wgUser;
229 $wgUser = User::newFromId( 0 );
230 // Having a user with id = 0 safeguards us from DB access via User::loadOptions()
231
232 // Set our custom <doclink> hook
233 global $wgHooks;
234 $wgHooks['ParserFirstCallInit'][] = array( $this, 'registerDocLink' );
235
236 $this->settings = $this->internalDefaults;
237 foreach ( $this->defaultVarNames as $var ) {
238 $this->settings[$var] = $GLOBALS[$var];
239 }
240 foreach ( $this->dbTypes as $type ) {
241 $installer = $this->getDBInstaller( $type );
242 if ( !$installer->isCompiled() ) {
243 continue;
244 }
245 $defaults = $installer->getGlobalDefaults();
246 foreach ( $installer->getGlobalNames() as $var ) {
247 if ( isset( $defaults[$var] ) ) {
248 $this->settings[$var] = $defaults[$var];
249 } else {
250 $this->settings[$var] = $GLOBALS[$var];
251 }
252 }
253 }
254
255 $this->parserTitle = Title::newFromText( 'Installer' );
256 $this->parserOptions = new ParserOptions;
257 $this->parserOptions->setEditSection( false );
258 }
259
260 /**
261 * UI interface for displaying a short message
262 * The parameters are like parameters to wfMsg().
263 * The messages will be in wikitext format, which will be converted to an
264 * output format such as HTML or text before being sent to the user.
265 */
266 abstract function showMessage( $msg /*, ... */ );
267
268 abstract function showStatusMessage( $status );
269
270 /**
271 * Get a list of known DB types
272 */
273 function getDBTypes() {
274 return $this->dbTypes;
275 }
276
277 /**
278 * Get an instance of InstallerDBType for the specified DB type
279 * @param $type Mixed: DB installer for which is needed, false to use default
280 */
281 function getDBInstaller( $type = false ) {
282 if ( !$type ) {
283 $type = $this->getVar( 'wgDBtype' );
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 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 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 if ( strstr( wfShellExec( $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;
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 public function installExtensions() {
859 global $wgHooks, $wgAutoloadClasses;
860 $exts = $this->getVar( '_Extensions' );
861 $path = $this->getVar( 'IP' ) . '/extensions';
862 foreach( $exts as $e ) {
863 require( "$path/$e/$e.php" );
864 }
865 return Status::newGood();
866 }
867
868 public function installDatabase() {
869 $installer = $this->getDBInstaller( $this->getVar( 'wgDBtype' ) );
870 $status = $installer->setupDatabase();
871 return $status;
872 }
873
874 public function installUser() {
875 $installer = $this->getDBInstaller( $this->getVar( 'wgDBtype' ) );
876 $status = $installer->setupUser();
877 return $status;
878 }
879
880 public function installTables() {
881 $installer = $this->getDBInstaller();
882 $status = $installer->createTables();
883 if( $status->isOK() ) {
884 LBFactory::enableBackend();
885 }
886 return $status;
887 }
888
889 public function installInterwiki() {
890 $installer = $this->getDBInstaller();
891 return $installer->populateInterwikiTable();
892 }
893
894 public function installSecretKey() {
895 if ( wfIsWindows() ) {
896 $file = null;
897 } else {
898 wfSuppressWarnings();
899 $file = fopen( "/dev/urandom", "r" );
900 wfRestoreWarnings();
901 }
902
903 $status = Status::newGood();
904
905 if ( $file ) {
906 $secretKey = bin2hex( fread( $file, 32 ) );
907 fclose( $file );
908 } else {
909 $secretKey = "";
910 for ( $i=0; $i<8; $i++ ) {
911 $secretKey .= dechex(mt_rand(0, 0x7fffffff));
912 }
913 $status->warning( 'config-insecure-secretkey' );
914 }
915 $this->setVar( 'wgSecretKey', $secretKey );
916
917 return $status;
918 }
919
920 public function installSysop() {
921 $name = $this->getVar( '_AdminName' );
922 $user = User::newFromName( $name );
923 if ( !$user ) {
924 // we should've validated this earlier anyway!
925 return Status::newFatal( 'config-admin-error-user', $name );
926 }
927 if ( $user->idForName() == 0 ) {
928 $user->addToDatabase();
929 try {
930 $user->setPassword( $this->getVar( '_AdminPassword' ) );
931 } catch( PasswordError $pwe ) {
932 return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() );
933 }
934 $user->addGroup( 'sysop' );
935 $user->addGroup( 'bureaucrat' );
936 $user->saveSettings();
937 }
938 return Status::newGood();
939 }
940
941 public function installLocalsettings() {
942 $localSettings = new LocalSettingsGenerator( $this );
943 return $localSettings->writeLocalSettings();
944 }
945
946 /**
947 * On POSIX systems return the primary group of the webserver we're running under.
948 * On other systems just returns null.
949 *
950 * This is used to advice the user that he should chgrp his config/data/images directory as the
951 * webserver user before he can install.
952 *
953 * Public because SqliteInstaller needs it, and doesn't subclass Installer.
954 *
955 * @return String
956 */
957 public static function maybeGetWebserverPrimaryGroup() {
958 if ( ! function_exists('posix_getegid') || ! function_exists('posix_getpwuid') ) {
959 # I don't know this, this isn't UNIX
960 return null;
961 }
962
963 # posix_getegid() *not* getmygid() because we want the group of the webserver,
964 # not whoever owns the current script
965 $gid = posix_getegid();
966 $getpwuid = posix_getpwuid( $gid );
967 $group = $getpwuid["name"];
968
969 return $group;
970 }
971
972 /**
973 * Override the necessary bits of the config to run an installation
974 */
975 public static function overrideConfig() {
976 define( 'MW_NO_SESSION', 1 );
977
978 // Don't access the database
979 $GLOBALS['wgUseDatabaseMessages'] = false;
980 // Debug-friendly
981 $GLOBALS['wgShowExceptionDetails'] = true;
982 // Don't break forms
983 $GLOBALS['wgExternalLinkTarget'] = '_blank';
984
985 // Extended debugging. Maybe disable before release?
986 $GLOBALS['wgShowSQLErrors'] = true;
987 $GLOBALS['wgShowDBErrorBacktrace'] = true;
988 }
989 }