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