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