4 * This documentation group collects source code files with deployment functionality.
6 * @defgroup Deployment Deployment
10 * Base installer class.
12 * This class provides the base for installation and update functionality
13 * for both MediaWiki core and extensions.
18 abstract class Installer
{
21 * TODO: make protected?
28 * Cached DB installer instances, access using getDBInstaller().
32 protected $dbInstallers = array();
35 * Minimum memory size in MB.
39 protected $minMemorySize = 50;
42 * Cached Title, used by parse().
46 protected $parserTitle;
49 * Cached ParserOptions, used by parse().
53 protected $parserOptions;
56 * Known database types. These correspond to the class names <type>Installer,
57 * and are also MediaWiki database types valid for $wgDBtype.
59 * To add a new type, create a <type>Installer class and a Database<type>
60 * class, and add a config-type-<type> message to MessagesEn.php.
64 protected static $dbTypes = array(
72 * A list of environment check methods called by doEnvironmentChecks().
73 * These may output warnings using showMessage(), and/or abort the
74 * installation process by returning false.
78 protected $envChecks = array(
81 'envCheckRegisterGlobals',
82 'envCheckMagicQuotes',
83 'envCheckMagicSybase',
94 'envCheckWriteableDir',
96 'envCheckShellLocale',
97 'envCheckUploadsDirectory',
102 * UI interface for displaying a short message
103 * The parameters are like parameters to wfMsg().
104 * The messages will be in wikitext format, which will be converted to an
105 * output format such as HTML or text before being sent to the user.
107 public abstract function showMessage( $msg /*, ... */ );
110 * Constructor, always call this from child classes.
112 public function __construct() {
113 // Disable the i18n cache and LoadBalancer
114 Language
::getLocalisationCache()->disableBackend();
115 LBFactory
::disableBackend();
119 * Get a list of known DB types.
121 public static function getDBTypes() {
122 return self
::$dbTypes;
126 * Do initial checks of the PHP environment. Set variables according to
127 * the observed environment.
129 * It's possible that this may be called under the CLI SAPI, not the SAPI
130 * that the wiki will primarily run under. In that case, the subclass should
131 * initialise variables such as wgScriptPath, before calling this function.
133 * Under the web subclass, it can already be assumed that PHP 5+ is in use
134 * and that sessions are working.
138 public function doEnvironmentChecks() {
139 $this->showMessage( 'config-env-php', phpversion() );
143 foreach ( $this->envChecks
as $check ) {
144 $status = $this->$check();
145 if ( $status === false ) {
150 $this->setVar( '_Environment', $good );
153 $this->showMessage( 'config-env-good' );
155 $this->showMessage( 'config-env-bad' );
162 * Set a MW configuration variable, or internal installer configuration variable.
164 * @param $name String
165 * @param $value Mixed
167 public function setVar( $name, $value ) {
168 $this->settings
[$name] = $value;
172 * Get an MW configuration variable, or internal installer configuration variable.
173 * The defaults come from $GLOBALS (ultimately DefaultSettings.php).
174 * Installer variables are typically prefixed by an underscore.
176 * @param $name String
177 * @param $default Mixed
181 public function getVar( $name, $default = null ) {
182 if ( !isset( $this->settings
[$name] ) ) {
185 return $this->settings
[$name];
190 * Get an instance of DatabaseInstaller for the specified DB type.
192 * @param $type Mixed: DB installer for which is needed, false to use default.
194 * @return DatabaseInstaller
196 public function getDBInstaller( $type = false ) {
198 $type = $this->getVar( 'wgDBtype' );
201 $type = strtolower( $type );
203 if ( !isset( $this->dbInstallers
[$type] ) ) {
204 $class = ucfirst( $type ). 'Installer';
205 $this->dbInstallers
[$type] = new $class( $this );
208 return $this->dbInstallers
[$type];
212 * Determine if LocalSettings exists. If it does, return an appropriate
213 * status for whether we should can upgrade or not.
217 public function getLocalSettingsStatus() {
220 $status = Status
::newGood();
222 wfSuppressWarnings();
223 $ls = file_exists( "$IP/LocalSettings.php" );
227 if( $this->getDBInstaller()->needsUpgrade() ) {
228 $status->warning( 'config-localsettings-upgrade' );
231 $status->fatal( 'config-localsettings-noupgrade' );
239 * Get a fake password for sending back to the user in HTML.
240 * This is a security mechanism to avoid compromise of the password in the
241 * event of session ID compromise.
243 * @param $realPassword String
247 public function getFakePassword( $realPassword ) {
248 return str_repeat( '*', strlen( $realPassword ) );
252 * Set a variable which stores a password, except if the new value is a
253 * fake password in which case leave it as it is.
255 * @param $name String
256 * @param $value Mixed
258 public function setPassword( $name, $value ) {
259 if ( !preg_match( '/^\*+$/', $value ) ) {
260 $this->setVar( $name, $value );
265 * On POSIX systems return the primary group of the webserver we're running under.
266 * On other systems just returns null.
268 * This is used to advice the user that he should chgrp his config/data/images directory as the
269 * webserver user before he can install.
271 * Public because SqliteInstaller needs it, and doesn't subclass Installer.
275 public static function maybeGetWebserverPrimaryGroup() {
276 if ( !function_exists( 'posix_getegid' ) ||
!function_exists( 'posix_getpwuid' ) ) {
277 # I don't know this, this isn't UNIX.
281 # posix_getegid() *not* getmygid() because we want the group of the webserver,
282 # not whoever owns the current script.
283 $gid = posix_getegid();
284 $getpwuid = posix_getpwuid( $gid );
285 $group = $getpwuid['name'];
291 * Convert wikitext $text to HTML.
293 * This is potentially error prone since many parser features require a complete
294 * installed MW database. The solution is to just not use those features when you
295 * write your messages. This appears to work well enough. Basic formatting and
296 * external links work just fine.
298 * But in case a translator decides to throw in a #ifexist or internal link or
299 * whatever, this function is guarded to catch attempted DB access and to present
300 * some fallback text.
302 * @param $text String
303 * @param $lineStart Boolean
306 public function parse( $text, $lineStart = false ) {
310 $out = $wgParser->parse( $text, $this->parserTitle
, $this->parserOptions
, $lineStart );
311 $html = $out->getText();
312 } catch ( DBAccessError
$e ) {
313 $html = '<!--DB access attempted during parse--> ' . htmlspecialchars( $text );
315 if ( !empty( $this->debug
) ) {
316 $html .= "<!--\n" . $e->getTraceAsString() . "\n-->";
326 * @param $installer DatabaseInstaller
330 public function installDatabase( DatabaseInstaller
&$installer ) {
332 $type = $this->getVar( 'wgDBtype' );
333 $status = Status
::newFatal( "config-no-db", $type );
335 $status = $installer->setupDatabase();
344 * @param $installer DatabaseInstaller
348 public function installTables( DatabaseInstaller
&$installer ) {
349 $status = $installer->createTables();
351 if( $status->isOK() ) {
352 LBFactory
::enableBackend();
361 * @param $installer DatabaseInstaller
365 public function installInterwiki( DatabaseInstaller
&$installer ) {
366 return $installer->populateInterwikiTable();
370 * Exports all wg* variables stored by the installer into global scope.
372 public function exportVars() {
373 foreach ( $this->settings
as $name => $value ) {
374 if ( substr( $name, 0, 2 ) == 'wg' ) {
375 $GLOBALS[$name] = $value;
381 * Check if we're installing the latest version.
383 public function envLatestVersion() {
386 $repository = wfGetRepository();
387 $currentVersion = $repository->getLatestCoreVersion();
389 $this->setVar( '_ExternalHTTP', true );
391 if ( $currentVersion === false ) {
392 # For when the request is successful but there's e.g. some silly man in
393 # the middle firewall blocking us, e.g. one of those annoying airport ones
394 $this->showMessage( 'config-env-latest-can-not-check', $repository->getLocation() );
398 if( version_compare( $wgVersion, $currentVersion, '<' ) ) {
399 $this->showMessage( 'config-env-latest-old' );
400 // FIXME: this only works for the web installer!
401 $this->showHelpBox( 'config-env-latest-help', $wgVersion, $currentVersion );
402 } elseif( version_compare( $wgVersion, $currentVersion, '>' ) ) {
403 $this->showMessage( 'config-env-latest-new' );
406 $this->showMessage( 'config-env-latest-ok' );
410 * Environment check for DB types.
412 public function envCheckDB() {
415 $compiledDBs = array();
416 $goodNames = array();
419 foreach ( self
::getDBTypes() as $name ) {
420 $db = $this->getDBInstaller( $name );
421 $readableName = wfMsg( 'config-type-' . $name );
423 if ( $db->isCompiled() ) {
424 $compiledDBs[] = $name;
425 $goodNames[] = $readableName;
428 $allNames[] = $readableName;
431 $this->setVar( '_CompiledDBs', $compiledDBs );
433 if ( !$compiledDBs ) {
434 $this->showMessage( 'config-no-db' );
435 // FIXME: this only works for the web installer!
436 $this->showHelpBox( 'config-no-db-help', $wgLang->commaList( $allNames ) );
440 $this->showMessage( 'config-have-db', $wgLang->listToText( $goodNames ), count( $goodNames ) );
444 * Environment check for register_globals.
446 public function envCheckRegisterGlobals() {
447 if( wfIniGetBool( "magic_quotes_runtime" ) ) {
448 $this->showMessage( 'config-register-globals' );
453 * Environment check for magic_quotes_runtime.
455 public function envCheckMagicQuotes() {
456 if( wfIniGetBool( "magic_quotes_runtime" ) ) {
457 $this->showMessage( 'config-magic-quotes-runtime' );
463 * Environment check for magic_quotes_sybase.
465 public function envCheckMagicSybase() {
466 if ( wfIniGetBool( 'magic_quotes_sybase' ) ) {
467 $this->showMessage( 'config-magic-quotes-sybase' );
473 * Environment check for mbstring.func_overload.
475 public function envCheckMbstring() {
476 if ( wfIniGetBool( 'mbstring.func_overload' ) ) {
477 $this->showMessage( 'config-mbstring' );
483 * Environment check for zend.ze1_compatibility_mode.
485 public function envCheckZE1() {
486 if ( wfIniGetBool( 'zend.ze1_compatibility_mode' ) ) {
487 $this->showMessage( 'config-ze1' );
493 * Environment check for safe_mode.
495 public function envCheckSafeMode() {
496 if ( wfIniGetBool( 'safe_mode' ) ) {
497 $this->setVar( '_SafeMode', true );
498 $this->showMessage( 'config-safe-mode' );
503 * Environment check for the XML module.
505 public function envCheckXML() {
506 if ( !function_exists( "utf8_encode" ) ) {
507 $this->showMessage( 'config-xml-bad' );
510 $this->showMessage( 'config-xml-good' );
514 * Environment check for the PCRE module.
516 public function envCheckPCRE() {
517 if ( !function_exists( 'preg_match' ) ) {
518 $this->showMessage( 'config-pcre' );
524 * Environment check for available memory.
526 public function envCheckMemory() {
527 $limit = ini_get( 'memory_limit' );
529 if ( !$limit ||
$limit == -1 ) {
530 $this->showMessage( 'config-memory-none' );
534 $n = intval( $limit );
536 if( preg_match( '/^([0-9]+)[Mm]$/', trim( $limit ), $m ) ) {
537 $n = intval( $m[1] * ( 1024 * 1024 ) );
540 if( $n < $this->minMemorySize
* 1024 * 1024 ) {
541 $newLimit = "{$this->minMemorySize}M";
543 if( ini_set( "memory_limit", $newLimit ) === false ) {
544 $this->showMessage( 'config-memory-bad', $limit );
546 $this->showMessage( 'config-memory-raised', $limit, $newLimit );
547 $this->setVar( '_RaiseMemory', true );
550 $this->showMessage( 'config-memory-ok', $limit );
555 * Environment check for compiled object cache types.
557 public function envCheckCache() {
560 foreach ( $this->objectCaches
as $name => $function ) {
561 if ( function_exists( $function ) ) {
562 $caches[$name] = true;
563 $this->showMessage( 'config-' . $name );
568 $this->showMessage( 'config-no-cache' );
571 $this->setVar( '_Caches', $caches );
575 * Search for GNU diff3.
577 public function envCheckDiff3() {
578 $paths = array_merge(
586 explode( PATH_SEPARATOR
, getenv( "PATH" ) )
589 $names = array( "gdiff3", "diff3", "diff3.exe" );
590 $versionInfo = array( '$1 --version 2>&1', 'diff3 (GNU diffutils)' );
594 foreach ( $paths as $path ) {
595 $exe = $this->locateExecutable( $path, $names, $versionInfo );
597 if ($exe !== false) {
598 $this->setVar( 'wgDiff3', $exe );
605 $this->showMessage( 'config-diff3-good', $exe );
607 $this->setVar( 'wgDiff3', false );
608 $this->showMessage( 'config-diff3-bad' );
613 * Environment check for ImageMagick and GD.
615 public function envCheckGraphics() {
616 $imcheck = array( "/usr/bin", "/opt/csw/bin", "/usr/local/bin", "/sw/bin", "/opt/local/bin" );
618 foreach( $imcheck as $dir ) {
619 $im = "$dir/convert";
621 wfSuppressWarnings();
622 $file_exists = file_exists( $im );
626 $this->showMessage( 'config-imagemagick', $im );
627 $this->setVar( 'wgImageMagickConvertCommand', $im );
632 if ( function_exists( 'imagejpeg' ) ) {
633 $this->showMessage( 'config-gd' );
637 $this->showMessage( 'no-scaling' );
641 * Environment check for setting $IP and $wgScriptPath.
643 public function envCheckPath() {
645 $IP = dirname( dirname( dirname( __FILE__
) ) );
647 $this->setVar( 'IP', $IP );
648 $this->showMessage( 'config-dir', $IP );
650 // PHP_SELF isn't available sometimes, such as when PHP is CGI but
651 // cgi.fix_pathinfo is disabled. In that case, fall back to SCRIPT_NAME
652 // to get the path to the current script... hopefully it's reliable. SIGH
653 if ( !empty( $_SERVER['PHP_SELF'] ) ) {
654 $path = $_SERVER['PHP_SELF'];
655 } elseif ( !empty( $_SERVER['SCRIPT_NAME'] ) ) {
656 $path = $_SERVER['SCRIPT_NAME'];
657 } elseif ( $this->getVar( 'wgScriptPath' ) ) {
658 // Some kind soul has set it for us already (e.g. debconf)
661 $this->showMessage( 'config-no-uri' );
665 $uri = preg_replace( '{^(.*)/config.*$}', '$1', $path );
666 $this->setVar( 'wgScriptPath', $uri );
667 $this->showMessage( 'config-uri', $uri );
671 * Environment check for writable config/ directory.
673 public function envCheckWriteableDir() {
674 $ipDir = $this->getVar( 'IP' );
675 $configDir = $ipDir . '/config';
677 if( !is_writeable( $configDir ) ) {
678 $webserverGroup = self
::maybeGetWebserverPrimaryGroup();
680 if ( $webserverGroup !== null ) {
681 $this->showMessage( 'config-dir-not-writable-group', $ipDir, $webserverGroup );
683 $this->showMessage( 'config-dir-not-writable-nogroup', $ipDir, $webserverGroup );
691 * Environment check for setting the preferred PHP file extension.
693 public function envCheckExtension() {
694 // FIXME: detect this properly
695 if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) {
701 $this->setVar( 'wgScriptExtension', ".$ext" );
702 $this->showMessage( 'config-file-extension', $ext );
708 public function envCheckShellLocale() {
709 # Give up now if we're in safe mode or open_basedir.
710 # It's theoretically possible but tricky to work with.
711 if ( wfIniGetBool( "safe_mode" ) ||
ini_get( 'open_basedir' ) ||
!function_exists( 'exec' ) ) {
715 $os = php_uname( 's' );
716 $supported = array( 'Linux', 'SunOS', 'HP-UX' ); # Tested these
718 if ( !in_array( $os, $supported ) ) {
722 # Get a list of available locales.
723 $lines = $ret = false;
724 exec( '/usr/bin/locale -a', $lines, $ret );
730 $lines = wfArrayMap( 'trim', $lines );
731 $candidatesByLocale = array();
732 $candidatesByLang = array();
734 foreach ( $lines as $line ) {
735 if ( $line === '' ) {
739 if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) {
743 list( $all, $lang, $territory, $charset, $modifier ) = $m;
745 $candidatesByLocale[$m[0]] = $m;
746 $candidatesByLang[$lang][] = $m;
749 # Try the current value of LANG.
750 if ( isset( $candidatesByLocale[ getenv( 'LANG' ) ] ) ) {
751 $this->setVar( 'wgShellLocale', getenv( 'LANG' ) );
752 $this->showMessage( 'config-shell-locale', getenv( 'LANG' ) );
756 # Try the most common ones.
757 $commonLocales = array( 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' );
758 foreach ( $commonLocales as $commonLocale ) {
759 if ( isset( $candidatesByLocale[$commonLocale] ) ) {
760 $this->setVar( 'wgShellLocale', $commonLocale );
761 $this->showMessage( 'config-shell-locale', $commonLocale );
766 # Is there an available locale in the Wiki's language?
767 $wikiLang = $this->getVar( 'wgLanguageCode' );
769 if ( isset( $candidatesByLang[$wikiLang] ) ) {
770 $m = reset( $candidatesByLang[$wikiLang] );
771 $this->setVar( 'wgShellLocale', $m[0] );
772 $this->showMessage( 'config-shell-locale', $m[0] );
776 # Are there any at all?
777 if ( count( $candidatesByLocale ) ) {
778 $m = reset( $candidatesByLocale );
779 $this->setVar( 'wgShellLocale', $m[0] );
780 $this->showMessage( 'config-shell-locale', $m[0] );
791 public function envCheckUploadsDirectory() {
792 global $IP, $wgServer;
794 $dir = $IP . '/images/';
795 $url = $wgServer . $this->getVar( 'wgScriptPath' ) . '/images/';
796 $safe = !$this->dirIsExecutable( $dir, $url );
799 $this->showMessage( 'config-uploads-safe' );
801 $this->showMessage( 'config-uploads-not-safe', $dir );
806 * Convert a hex string representing a Unicode code point to that code point.
810 protected function unicodeChar( $c ) {
814 } else if ($c <= 0x7FF) {
815 return chr(0xC0 |
$c >> 6) . chr(0x80 |
$c & 0x3F);
816 } else if ($c <= 0xFFFF) {
817 return chr(0xE0 |
$c >> 12) . chr(0x80 |
$c >> 6 & 0x3F)
818 . chr(0x80 |
$c & 0x3F);
819 } else if ($c <= 0x10FFFF) {
820 return chr(0xF0 |
$c >> 18) . chr(0x80 |
$c >> 12 & 0x3F)
821 . chr(0x80 |
$c >> 6 & 0x3F)
822 . chr(0x80 |
$c & 0x3F);
830 * Check the libicu version
832 public function envCheckLibicu() {
833 $utf8 = function_exists( 'utf8_normalize' );
834 $intl = function_exists( 'normalizer_normalize' );
837 * This needs to be updated something that the latest libicu
838 * will properly normalize. This normalization was found at
839 * http://www.unicode.org/versions/Unicode5.2.0/#Character_Additions
840 * Note that we use the hex representation to create the code
841 * points in order to avoid any Unicode-destroying during transit.
843 $not_normal_c = $this->unicodeChar("FA6C");
844 $normal_c = $this->unicodeChar("242EE");
846 $useNormalizer = 'php';
847 $needsUpdate = false;
850 * We're going to prefer the pecl extension here unless
851 * utf8_normalize is more up to date.
854 $useNormalizer = 'utf8';
855 $utf8 = utf8_normalize( $not_normal_c, UNORM_NFC
);
856 if ( $utf8 !== $normal_c ) $needsUpdate = true;
859 $useNormalizer = 'intl';
860 $intl = normalizer_normalize( $not_normal_c, Normalizer
::FORM_C
);
861 if ( $intl !== $normal_c ) $needsUpdate = true;
864 // Uses messages 'config-unicode-using-php', 'config-unicode-using-utf8', 'config-unicode-using-intl'
865 $this->showMessage( 'config-unicode-using-' . $useNormalizer );
866 if( $useNormalizer === 'php' ) {
867 $this->showMessage( 'config-unicode-pure-php-warning' );
868 } elseif( $needsUpdate ) {
869 $this->showMessage( 'config-unicode-update-warning' );
875 * Search a path for any of the given executable names. Returns the
876 * executable name if found. Also checks the version string returned
877 * by each executable.
879 * Used only by environment checks.
881 * @param $path String: path to search
882 * @param $names Array of executable names
883 * @param $versionInfo Boolean false or array with two members:
884 * 0 => Command to run for version check, with $1 for the path
885 * 1 => String to compare the output with
887 * If $versionInfo is not false, only executables with a version
888 * matching $versionInfo[1] will be returned.
890 protected function locateExecutable( $path, $names, $versionInfo = false ) {
891 if ( !is_array( $names ) ) {
892 $names = array( $names );
895 foreach ( $names as $name ) {
896 $command = "$path/$name";
898 wfSuppressWarnings();
899 $file_exists = file_exists( $command );
902 if ( $file_exists ) {
903 if ( !$versionInfo ) {
907 $file = str_replace( '$1', $command, $versionInfo[0] );
909 # Should maybe be wfShellExec( $file), but runs into a ulimit, see
910 # http://www.mediawiki.org/w/index.php?title=New-installer_issues&diff=prev&oldid=335456
911 if ( strstr( `
$file`
, $versionInfo[1]) !== false ) {
921 * Checks if scripts located in the given directory can be executed via the given URL.
923 * Used only by environment checks.
925 public function dirIsExecutable( $dir, $url ) {
926 $scriptTypes = array(
928 "<?php echo 'ex' . 'ec';",
929 "#!/var/env php5\n<?php echo 'ex' . 'ec';",
933 // it would be good to check other popular languages here, but it'll be slow.
935 wfSuppressWarnings();
937 foreach ( $scriptTypes as $ext => $contents ) {
938 foreach ( $contents as $source ) {
939 $file = 'exectest.' . $ext;
941 if ( !file_put_contents( $dir . $file, $source ) ) {
945 $text = Http
::get( $url . $file );
946 unlink( $dir . $file );
948 if ( $text == 'exec' ) {