@ -> wfSuppressWarnings and wfRestoreWarnings
[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 wfSuppressWarnings();
658 $file_exists = file_exists( $command );
659 wfRestoreWarnings();
660
661 if ( $file_exists ) {
662 if ( !$versionInfo ) {
663 return $command;
664 }
665
666 $file = str_replace( '$1', $command, $versionInfo[0] );
667
668 # Should maybe be wfShellExec( $file), but runs into a ulimit, see
669 # http://www.mediawiki.org/w/index.php?title=New-installer_issues&diff=prev&oldid=335456
670 if ( strstr( `$file`, $versionInfo[1]) !== false ) {
671 return $command;
672 }
673 }
674 }
675
676 return false;
677 }
678
679 /**
680 * Environment check for ImageMagick and GD.
681 */
682 public function envCheckGraphics() {
683 $imcheck = array( "/usr/bin", "/opt/csw/bin", "/usr/local/bin", "/sw/bin", "/opt/local/bin" );
684
685 foreach( $imcheck as $dir ) {
686 $im = "$dir/convert";
687
688 wfSuppressWarnings();
689 $file_exists = file_exists( $im );
690 wfRestoreWarnings();
691
692 if( $file_exists ) {
693 $this->showMessage( 'config-imagemagick', $im );
694 $this->setVar( 'wgImageMagickConvertCommand', $im );
695 return true;
696 }
697 }
698 if ( function_exists( 'imagejpeg' ) ) {
699 $this->showMessage( 'config-gd' );
700 return true;
701 }
702 $this->showMessage( 'no-scaling' );
703 }
704
705 /** Environment check for setting $IP and $wgScriptPath */
706 public function envCheckPath() {
707 $IP = dirname( dirname( dirname( __FILE__ ) ) );
708 $this->setVar( 'IP', $IP );
709 $this->showMessage( 'config-dir', $IP );
710
711 // PHP_SELF isn't available sometimes, such as when PHP is CGI but
712 // cgi.fix_pathinfo is disabled. In that case, fall back to SCRIPT_NAME
713 // to get the path to the current script... hopefully it's reliable. SIGH
714 if ( !empty( $_SERVER['PHP_SELF'] ) ) {
715 $path = $_SERVER['PHP_SELF'];
716 } elseif ( !empty( $_SERVER['SCRIPT_NAME'] ) ) {
717 $path = $_SERVER['SCRIPT_NAME'];
718 } elseif ( $this->getVar( 'wgScriptPath' ) ) {
719 // Some kind soul has set it for us already (e.g. debconf)
720 return true;
721 } else {
722 $this->showMessage( 'config-no-uri' );
723 return false;
724 }
725 $uri = preg_replace( '{^(.*)/config.*$}', '$1', $path );
726 $this->setVar( 'wgScriptPath', $uri );
727 $this->showMessage( 'config-uri', $uri );
728 }
729
730 /** Environment check for writable config/ directory */
731 public function envCheckWriteableDir() {
732 $ipDir = $this->getVar( 'IP' );
733 $configDir = $ipDir . '/config';
734 if( !is_writeable( $configDir ) ) {
735 $webserverGroup = self::maybeGetWebserverPrimaryGroup();
736 if ( $webserverGroup !== null ) {
737 $this->showMessage( 'config-dir-not-writable-group', $ipDir, $webserverGroup );
738 } else {
739 $this->showMessage( 'config-dir-not-writable-nogroup', $ipDir, $webserverGroup );
740 }
741 return false;
742 }
743 }
744
745 /** Environment check for setting the preferred PHP file extension */
746 public function envCheckExtension() {
747 // FIXME: detect this properly
748 if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) {
749 $ext = 'php5';
750 } else {
751 $ext = 'php';
752 }
753 $this->setVar( 'wgScriptExtension', ".$ext" );
754 $this->showMessage( 'config-file-extension', $ext );
755 }
756
757 public function envCheckShellLocale() {
758 # Give up now if we're in safe mode or open_basedir
759 # It's theoretically possible but tricky to work with
760 if ( wfIniGetBool( "safe_mode" ) || ini_get( 'open_basedir' ) || !function_exists( 'exec' ) ) {
761 return true;
762 }
763
764 $os = php_uname( 's' );
765 $supported = array( 'Linux', 'SunOS', 'HP-UX' ); # Tested these
766 if ( !in_array( $os, $supported ) ) {
767 return true;
768 }
769
770 # Get a list of available locales
771 $lines = $ret = false;
772 exec( '/usr/bin/locale -a', $lines, $ret );
773 if ( $ret ) {
774 return true;
775 }
776
777 $lines = wfArrayMap( 'trim', $lines );
778 $candidatesByLocale = array();
779 $candidatesByLang = array();
780 foreach ( $lines as $line ) {
781 if ( $line === '' ) {
782 continue;
783 }
784 if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) {
785 continue;
786 }
787 list( $all, $lang, $territory, $charset, $modifier ) = $m;
788 $candidatesByLocale[$m[0]] = $m;
789 $candidatesByLang[$lang][] = $m;
790 }
791
792 # Try the current value of LANG
793 if ( isset( $candidatesByLocale[ getenv( 'LANG' ) ] ) ) {
794 $this->setVar( 'wgShellLocale', getenv( 'LANG' ) );
795 $this->showMessage( 'config-shell-locale', getenv( 'LANG' ) );
796 return true;
797 }
798
799 # Try the most common ones
800 $commonLocales = array( 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' );
801 foreach ( $commonLocales as $commonLocale ) {
802 if ( isset( $candidatesByLocale[$commonLocale] ) ) {
803 $this->setVar( 'wgShellLocale', $commonLocale );
804 $this->showMessage( 'config-shell-locale', $commonLocale );
805 return true;
806 }
807 }
808
809 # Is there an available locale in the Wiki's language?
810 $wikiLang = $this->getVar( 'wgLanguageCode' );
811 if ( isset( $candidatesByLang[$wikiLang] ) ) {
812 $m = reset( $candidatesByLang[$wikiLang] );
813 $this->setVar( 'wgShellLocale', $m[0] );
814 $this->showMessage( 'config-shell-locale', $m[0] );
815 return true;
816 }
817
818 # Are there any at all?
819 if ( count( $candidatesByLocale ) ) {
820 $m = reset( $candidatesByLocale );
821 $this->setVar( 'wgShellLocale', $m[0] );
822 $this->showMessage( 'config-shell-locale', $m[0] );
823 return true;
824 }
825
826 # Give up
827 return true;
828 }
829
830 public function envCheckUploadsDirectory() {
831 global $IP, $wgServer;
832 $dir = $IP . '/images/';
833 $url = $wgServer . $this->getVar( 'wgScriptPath' ) . '/images/';
834 $safe = !$this->dirIsExecutable( $dir, $url );
835 if ( $safe ) {
836 $this->showMessage( 'config-uploads-safe' );
837 } else {
838 $this->showMessage( 'config-uploads-not-safe', $dir );
839 }
840 }
841
842 /**
843 * Checks if scripts located in the given directory can be executed via the given URL.
844 */
845 public function dirIsExecutable( $dir, $url ) {
846 $scriptTypes = array(
847 'php' => array(
848 "<?php echo 'ex' . 'ec';",
849 "#!/var/env php5\n<?php echo 'ex' . 'ec';",
850 ),
851 );
852 // it would be good to check other popular languages here, but it'll be slow
853
854 wfSuppressWarnings();
855 foreach ( $scriptTypes as $ext => $contents ) {
856 foreach ( $contents as $source ) {
857 $file = 'exectest.' . $ext;
858 if ( !file_put_contents( $dir . $file, $source ) ) {
859 break;
860 }
861 $text = Http::get( $url . $file );
862 unlink( $dir . $file );
863 if ( $text == 'exec' ) {
864 wfRestoreWarnings();
865 return $ext;
866 }
867 }
868 }
869 wfRestoreWarnings();
870 return false;
871 }
872
873 /**
874 * Convert wikitext $text to HTML.
875 *
876 * This is potentially error prone since many parser features require a complete
877 * installed MW database. The solution is to just not use those features when you
878 * write your messages. This appears to work well enough. Basic formatting and
879 * external links work just fine.
880 *
881 * But in case a translator decides to throw in a #ifexist or internal link or
882 * whatever, this function is guarded to catch attempted DB access and to present
883 * some fallback text.
884 *
885 * @param $text String
886 * @param $lineStart Boolean
887 * @return String
888 */
889 public function parse( $text, $lineStart = false ) {
890 global $wgParser;
891 try {
892 $out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
893 $html = $out->getText();
894 } catch ( DBAccessError $e ) {
895 $html = '<!--DB access attempted during parse--> ' . htmlspecialchars( $text );
896 if ( !empty( $this->debug ) ) {
897 $html .= "<!--\n" . $e->getTraceAsString() . "\n-->";
898 }
899 }
900 return $html;
901 }
902
903 /**
904 * Register tag hook below.
905 */
906 public function registerDocLink( &$parser ) {
907 $parser->setHook( 'doclink', array( $this, 'docLink' ) );
908 return true;
909 }
910
911 /**
912 * Extension tag hook for a documentation link.
913 */
914 public function docLink( $linkText, $attribs, $parser ) {
915 $url = $this->getDocUrl( $attribs['href'] );
916 return '<a href="' . htmlspecialchars( $url ) . '">' .
917 htmlspecialchars( $linkText ) .
918 '</a>';
919 }
920
921 /**
922 * Overridden by WebInstaller to provide lastPage parameters.
923 */
924 protected function getDocUrl( $page ) {
925 return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $attribs['href'] );
926 }
927
928 public function findExtensions() {
929 if( $this->getVar( 'IP' ) === null ) {
930 return false;
931 }
932 $exts = array();
933 $dir = $this->getVar( 'IP' ) . '/extensions';
934 $dh = opendir( $dir );
935 while ( ( $file = readdir( $dh ) ) !== false ) {
936 if( file_exists( "$dir/$file/$file.php" ) ) {
937 $exts[$file] = null;
938 }
939 }
940 $this->setVar( '_Extensions', $exts );
941 return $exts;
942 }
943
944 public function getInstallSteps() {
945 if( $this->getVar( '_UpgradeDone' ) ) {
946 $this->installSteps = array( 'localsettings' );
947 }
948 if( count( $this->getVar( '_Extensions' ) ) ) {
949 array_unshift( $this->installSteps, 'extensions' );
950 }
951 return $this->installSteps;
952 }
953
954 /**
955 * Actually perform the installation.
956 * @param Array $startCB A callback array for the beginning of each step
957 * @param Array $endCB A callback array for the end of each step
958 * @return Array of Status objects
959 */
960 public function performInstallation( $startCB, $endCB ) {
961 $installResults = array();
962 $installer = $this->getDBInstaller();
963 foreach( $this->getInstallSteps() as $stepObj ) {
964 $step = is_array( $stepObj ) ? $stepObj['name'] : $stepObj;
965 call_user_func_array( $startCB, array( $step ) );
966 $status = null;
967
968 # Call our working function
969 if ( is_array( $stepObj ) ) {
970 # A custom callaback
971 $callback = $stepObj['callback'];
972 $status = call_user_func_array( $callback, array( $installer ) );
973 } else {
974 # Boring implicitly named callback
975 $func = 'install' . ucfirst( $step );
976 $status = $this->{$func}( $installer );
977 }
978 call_user_func_array( $endCB, array( $step, $status ) );
979 $installResults[$step] = $status;
980
981 // If we've hit some sort of fatal, we need to bail. Callback
982 // already had a chance to do output above.
983 if( !$status->isOk() )
984 break;
985 }
986 if( $status->isOk() ) {
987 $this->setVar( '_InstallDone', true );
988 }
989 return $installResults;
990 }
991
992 public function installExtensions() {
993 global $wgHooks, $wgAutoloadClasses;
994 $exts = $this->getVar( '_Extensions' );
995 $path = $this->getVar( 'IP' ) . '/extensions';
996 foreach( $exts as $e ) {
997 require( "$path/$e/$e.php" );
998 }
999 return Status::newGood();
1000 }
1001
1002 public function installDatabase( &$installer ) {
1003 if(!$installer) {
1004 $type = $this->getVar( 'wgDBtype' );
1005 $status = Status::newFatal( "config-no-db", $type );
1006 } else {
1007 $status = $installer->setupDatabase();
1008 }
1009 return $status;
1010 }
1011
1012 public function installTables( &$installer ) {
1013 $status = $installer->createTables();
1014 if( $status->isOK() ) {
1015 LBFactory::enableBackend();
1016 }
1017 return $status;
1018 }
1019
1020 public function installInterwiki( &$installer ) {
1021 return $installer->populateInterwikiTable();
1022 }
1023
1024 public function installSecretKey() {
1025 if ( wfIsWindows() ) {
1026 $file = null;
1027 } else {
1028 wfSuppressWarnings();
1029 $file = fopen( "/dev/urandom", "r" );
1030 wfRestoreWarnings();
1031 }
1032
1033 $status = Status::newGood();
1034
1035 if ( $file ) {
1036 $secretKey = bin2hex( fread( $file, 32 ) );
1037 fclose( $file );
1038 } else {
1039 $secretKey = "";
1040 for ( $i=0; $i<8; $i++ ) {
1041 $secretKey .= dechex(mt_rand(0, 0x7fffffff));
1042 }
1043 $status->warning( 'config-insecure-secretkey' );
1044 }
1045 $this->setVar( 'wgSecretKey', $secretKey );
1046
1047 return $status;
1048 }
1049
1050 public function installSysop() {
1051 $name = $this->getVar( '_AdminName' );
1052 $user = User::newFromName( $name );
1053 if ( !$user ) {
1054 // we should've validated this earlier anyway!
1055 return Status::newFatal( 'config-admin-error-user', $name );
1056 }
1057 if ( $user->idForName() == 0 ) {
1058 $user->addToDatabase();
1059 try {
1060 $user->setPassword( $this->getVar( '_AdminPassword' ) );
1061 } catch( PasswordError $pwe ) {
1062 return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() );
1063 }
1064 $user->addGroup( 'sysop' );
1065 $user->addGroup( 'bureaucrat' );
1066 $user->saveSettings();
1067 }
1068 return Status::newGood();
1069 }
1070
1071 /**
1072 * Determine if LocalSettings exists. If it does, return an appropriate
1073 * status for whether we should can upgrade or not.
1074 * @return Status
1075 */
1076 public function getLocalSettingsStatus() {
1077 global $IP;
1078
1079 $status = Status::newGood();
1080
1081 wfSuppressWarnings();
1082 $ls = file_exists( "$IP/LocalSettings.php" );
1083 wfRestoreWarnings();
1084
1085 if( $ls ) {
1086 if( $this->getDBInstaller()->needsUpgrade() ) {
1087 $status->warning( 'config-localsettings-upgrade' );
1088 }
1089 else {
1090 $status->fatal( 'config-localsettings-noupgrade' );
1091 }
1092 }
1093 return $status;
1094 }
1095
1096 /**
1097 * On POSIX systems return the primary group of the webserver we're running under.
1098 * On other systems just returns null.
1099 *
1100 * This is used to advice the user that he should chgrp his config/data/images directory as the
1101 * webserver user before he can install.
1102 *
1103 * Public because SqliteInstaller needs it, and doesn't subclass Installer.
1104 *
1105 * @return String
1106 */
1107 public static function maybeGetWebserverPrimaryGroup() {
1108 if ( ! function_exists('posix_getegid') || ! function_exists('posix_getpwuid') ) {
1109 # I don't know this, this isn't UNIX
1110 return null;
1111 }
1112
1113 # posix_getegid() *not* getmygid() because we want the group of the webserver,
1114 # not whoever owns the current script.
1115 $gid = posix_getegid();
1116 $getpwuid = posix_getpwuid( $gid );
1117 $group = $getpwuid["name"];
1118
1119 return $group;
1120 }
1121
1122 /**
1123 * Override the necessary bits of the config to run an installation.
1124 */
1125 public static function overrideConfig() {
1126 define( 'MW_NO_SESSION', 1 );
1127
1128 // Don't access the database
1129 $GLOBALS['wgUseDatabaseMessages'] = false;
1130 // Debug-friendly
1131 $GLOBALS['wgShowExceptionDetails'] = true;
1132 // Don't break forms
1133 $GLOBALS['wgExternalLinkTarget'] = '_blank';
1134
1135 // Extended debugging. Maybe disable before release?
1136 $GLOBALS['wgShowSQLErrors'] = true;
1137 $GLOBALS['wgShowDBErrorBacktrace'] = true;
1138 }
1139
1140 /**
1141 * Add an installation step following the given step.
1142 * @param $findStep String the step to find. Use NULL to put the step at the beginning.
1143 * @param $callback array
1144 */
1145 public function addInstallStepFollowing( $findStep, $callback ) {
1146 $where = 0;
1147 if( $findStep !== null ) $where = array_search( $findStep, $this->installSteps );
1148
1149 array_splice( $this->installSteps, $where, 0, $callback );
1150 }
1151
1152 }