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