MediaWiki
REL1_24
|
00001 <?php 00039 abstract class Installer { 00040 00047 const MINIMUM_PCRE_VERSION = '7.2'; 00048 00052 protected $settings; 00053 00059 protected $compiledDBs; 00060 00066 protected $dbInstallers = array(); 00067 00073 protected $minMemorySize = 50; 00074 00080 protected $parserTitle; 00081 00087 protected $parserOptions; 00088 00098 protected static $dbTypes = array( 00099 'mysql', 00100 'postgres', 00101 'oracle', 00102 'mssql', 00103 'sqlite', 00104 ); 00105 00117 protected $envChecks = array( 00118 'envCheckDB', 00119 'envCheckRegisterGlobals', 00120 'envCheckBrokenXML', 00121 'envCheckMagicQuotes', 00122 'envCheckMbstring', 00123 'envCheckSafeMode', 00124 'envCheckXML', 00125 'envCheckPCRE', 00126 'envCheckMemory', 00127 'envCheckCache', 00128 'envCheckModSecurity', 00129 'envCheckDiff3', 00130 'envCheckGraphics', 00131 'envCheckGit', 00132 'envCheckServer', 00133 'envCheckPath', 00134 'envCheckShellLocale', 00135 'envCheckUploadsDirectory', 00136 'envCheckLibicu', 00137 'envCheckSuhosinMaxValueLength', 00138 'envCheckCtype', 00139 'envCheckIconv', 00140 'envCheckJSON', 00141 ); 00142 00148 protected $envPreps = array( 00149 'envPrepExtension', 00150 'envPrepServer', 00151 'envPrepPath', 00152 ); 00153 00161 protected $defaultVarNames = array( 00162 'wgSitename', 00163 'wgPasswordSender', 00164 'wgLanguageCode', 00165 'wgRightsIcon', 00166 'wgRightsText', 00167 'wgRightsUrl', 00168 'wgMainCacheType', 00169 'wgEnableEmail', 00170 'wgEnableUserEmail', 00171 'wgEnotifUserTalk', 00172 'wgEnotifWatchlist', 00173 'wgEmailAuthentication', 00174 'wgDBtype', 00175 'wgDiff3', 00176 'wgImageMagickConvertCommand', 00177 'wgGitBin', 00178 'IP', 00179 'wgScriptPath', 00180 'wgScriptExtension', 00181 'wgMetaNamespace', 00182 'wgDeletedDirectory', 00183 'wgEnableUploads', 00184 'wgShellLocale', 00185 'wgSecretKey', 00186 'wgUseInstantCommons', 00187 'wgUpgradeKey', 00188 'wgDefaultSkin', 00189 'wgResourceLoaderMaxQueryLength', 00190 ); 00191 00199 protected $internalDefaults = array( 00200 '_UserLang' => 'en', 00201 '_Environment' => false, 00202 '_SafeMode' => false, 00203 '_RaiseMemory' => false, 00204 '_UpgradeDone' => false, 00205 '_InstallDone' => false, 00206 '_Caches' => array(), 00207 '_InstallPassword' => '', 00208 '_SameAccount' => true, 00209 '_CreateDBAccount' => false, 00210 '_NamespaceType' => 'site-name', 00211 '_AdminName' => '', // will be set later, when the user selects language 00212 '_AdminPassword' => '', 00213 '_AdminPasswordConfirm' => '', 00214 '_AdminEmail' => '', 00215 '_Subscribe' => false, 00216 '_SkipOptional' => 'continue', 00217 '_RightsProfile' => 'wiki', 00218 '_LicenseCode' => 'none', 00219 '_CCDone' => false, 00220 '_Extensions' => array(), 00221 '_Skins' => array(), 00222 '_MemCachedServers' => '', 00223 '_UpgradeKeySupplied' => false, 00224 '_ExistingDBSettings' => false, 00225 00226 // $wgLogo is probably wrong (bug 48084); set something that will work. 00227 // Single quotes work fine here, as LocalSettingsGenerator outputs this unescaped. 00228 'wgLogo' => '$wgScriptPath/resources/assets/wiki.png', 00229 ); 00230 00236 private $installSteps = array(); 00237 00243 protected $extraInstallSteps = array(); 00244 00250 protected $objectCaches = array( 00251 'xcache' => 'xcache_get', 00252 'apc' => 'apc_fetch', 00253 'wincache' => 'wincache_ucache_get' 00254 ); 00255 00261 public $rightsProfiles = array( 00262 'wiki' => array(), 00263 'no-anon' => array( 00264 '*' => array( 'edit' => false ) 00265 ), 00266 'fishbowl' => array( 00267 '*' => array( 00268 'createaccount' => false, 00269 'edit' => false, 00270 ), 00271 ), 00272 'private' => array( 00273 '*' => array( 00274 'createaccount' => false, 00275 'edit' => false, 00276 'read' => false, 00277 ), 00278 ), 00279 ); 00280 00286 public $licenses = array( 00287 'cc-by' => array( 00288 'url' => 'http://creativecommons.org/licenses/by/3.0/', 00289 'icon' => '{$wgResourceBasePath}/resources/assets/licenses/cc-by.png', 00290 ), 00291 'cc-by-sa' => array( 00292 'url' => 'http://creativecommons.org/licenses/by-sa/3.0/', 00293 'icon' => '{$wgResourceBasePath}/resources/assets/licenses/cc-by-sa.png', 00294 ), 00295 'cc-by-nc-sa' => array( 00296 'url' => 'http://creativecommons.org/licenses/by-nc-sa/3.0/', 00297 'icon' => '{$wgResourceBasePath}/resources/assets/licenses/cc-by-nc-sa.png', 00298 ), 00299 'cc-0' => array( 00300 'url' => 'https://creativecommons.org/publicdomain/zero/1.0/', 00301 'icon' => '{$wgResourceBasePath}/resources/assets/licenses/cc-0.png', 00302 ), 00303 'pd' => array( 00304 'url' => '', 00305 'icon' => '{$wgResourceBasePath}/resources/assets/licenses/public-domain.png', 00306 ), 00307 'gfdl' => array( 00308 'url' => 'http://www.gnu.org/copyleft/fdl.html', 00309 'icon' => '{$wgResourceBasePath}/resources/assets/licenses/gnu-fdl.png', 00310 ), 00311 'none' => array( 00312 'url' => '', 00313 'icon' => '', 00314 'text' => '' 00315 ), 00316 'cc-choose' => array( 00317 // Details will be filled in by the selector. 00318 'url' => '', 00319 'icon' => '', 00320 'text' => '', 00321 ), 00322 ); 00323 00327 protected $mediaWikiAnnounceUrl = 00328 'https://lists.wikimedia.org/mailman/subscribe/mediawiki-announce'; 00329 00333 protected $mediaWikiAnnounceLanguages = array( 00334 'ca', 'cs', 'da', 'de', 'en', 'es', 'et', 'eu', 'fi', 'fr', 'hr', 'hu', 00335 'it', 'ja', 'ko', 'lt', 'nl', 'no', 'pl', 'pt', 'pt-br', 'ro', 'ru', 00336 'sl', 'sr', 'sv', 'tr', 'uk' 00337 ); 00338 00346 abstract public function showMessage( $msg /*, ... */ ); 00347 00352 abstract public function showError( $msg /*, ... */ ); 00353 00358 abstract public function showStatusMessage( Status $status ); 00359 00363 public function __construct() { 00364 global $wgMessagesDirs, $wgUser; 00365 00366 // Disable the i18n cache 00367 Language::getLocalisationCache()->disableBackend(); 00368 // Disable LoadBalancer and wfGetDB etc. 00369 LBFactory::disableBackend(); 00370 00371 // Disable object cache (otherwise CACHE_ANYTHING will try CACHE_DB and 00372 // SqlBagOStuff will then throw since we just disabled wfGetDB) 00373 $GLOBALS['wgMemc'] = new EmptyBagOStuff; 00374 ObjectCache::clear(); 00375 $emptyCache = array( 'class' => 'EmptyBagOStuff' ); 00376 $GLOBALS['wgObjectCaches'] = array( 00377 CACHE_NONE => $emptyCache, 00378 CACHE_DB => $emptyCache, 00379 CACHE_ANYTHING => $emptyCache, 00380 CACHE_MEMCACHED => $emptyCache, 00381 ); 00382 00383 // Load the installer's i18n. 00384 $wgMessagesDirs['MediawikiInstaller'] = __DIR__ . '/i18n'; 00385 00386 // Having a user with id = 0 safeguards us from DB access via User::loadOptions(). 00387 $wgUser = User::newFromId( 0 ); 00388 00389 $this->settings = $this->internalDefaults; 00390 00391 foreach ( $this->defaultVarNames as $var ) { 00392 $this->settings[$var] = $GLOBALS[$var]; 00393 } 00394 00395 $this->doEnvironmentPreps(); 00396 00397 $this->compiledDBs = array(); 00398 foreach ( self::getDBTypes() as $type ) { 00399 $installer = $this->getDBInstaller( $type ); 00400 00401 if ( !$installer->isCompiled() ) { 00402 continue; 00403 } 00404 $this->compiledDBs[] = $type; 00405 } 00406 00407 $this->parserTitle = Title::newFromText( 'Installer' ); 00408 $this->parserOptions = new ParserOptions; // language will be wrong :( 00409 $this->parserOptions->setEditSection( false ); 00410 } 00411 00417 public static function getDBTypes() { 00418 return self::$dbTypes; 00419 } 00420 00434 public function doEnvironmentChecks() { 00435 // Php version has already been checked by entry scripts 00436 // Show message here for information purposes 00437 if ( wfIsHHVM() ) { 00438 $this->showMessage( 'config-env-hhvm', HHVM_VERSION ); 00439 } else { 00440 $this->showMessage( 'config-env-php', PHP_VERSION ); 00441 } 00442 00443 $good = true; 00444 // Must go here because an old version of PCRE can prevent other checks from completing 00445 list( $pcreVersion ) = explode( ' ', PCRE_VERSION, 2 ); 00446 if ( version_compare( $pcreVersion, self::MINIMUM_PCRE_VERSION, '<' ) ) { 00447 $this->showError( 'config-pcre-old', self::MINIMUM_PCRE_VERSION, $pcreVersion ); 00448 $good = false; 00449 } else { 00450 foreach ( $this->envChecks as $check ) { 00451 $status = $this->$check(); 00452 if ( $status === false ) { 00453 $good = false; 00454 } 00455 } 00456 } 00457 00458 $this->setVar( '_Environment', $good ); 00459 00460 return $good ? Status::newGood() : Status::newFatal( 'config-env-bad' ); 00461 } 00462 00463 public function doEnvironmentPreps() { 00464 foreach ( $this->envPreps as $prep ) { 00465 $this->$prep(); 00466 } 00467 } 00468 00475 public function setVar( $name, $value ) { 00476 $this->settings[$name] = $value; 00477 } 00478 00489 public function getVar( $name, $default = null ) { 00490 if ( !isset( $this->settings[$name] ) ) { 00491 return $default; 00492 } else { 00493 return $this->settings[$name]; 00494 } 00495 } 00496 00502 public function getCompiledDBs() { 00503 return $this->compiledDBs; 00504 } 00505 00513 public function getDBInstaller( $type = false ) { 00514 if ( !$type ) { 00515 $type = $this->getVar( 'wgDBtype' ); 00516 } 00517 00518 $type = strtolower( $type ); 00519 00520 if ( !isset( $this->dbInstallers[$type] ) ) { 00521 $class = ucfirst( $type ) . 'Installer'; 00522 $this->dbInstallers[$type] = new $class( $this ); 00523 } 00524 00525 return $this->dbInstallers[$type]; 00526 } 00527 00533 public static function getExistingLocalSettings() { 00534 global $IP; 00535 00536 // You might be wondering why this is here. Well if you don't do this 00537 // then some poorly-formed extensions try to call their own classes 00538 // after immediately registering them. We really need to get extension 00539 // registration out of the global scope and into a real format. 00540 // @see https://bugzilla.wikimedia.org/67440 00541 global $wgAutoloadClasses; 00542 $wgAutoloadClasses = array(); 00543 00544 wfSuppressWarnings(); 00545 $_lsExists = file_exists( "$IP/LocalSettings.php" ); 00546 wfRestoreWarnings(); 00547 00548 if ( !$_lsExists ) { 00549 return false; 00550 } 00551 unset( $_lsExists ); 00552 00553 require "$IP/includes/DefaultSettings.php"; 00554 require "$IP/LocalSettings.php"; 00555 00556 return get_defined_vars(); 00557 } 00558 00568 public function getFakePassword( $realPassword ) { 00569 return str_repeat( '*', strlen( $realPassword ) ); 00570 } 00571 00579 public function setPassword( $name, $value ) { 00580 if ( !preg_match( '/^\*+$/', $value ) ) { 00581 $this->setVar( $name, $value ); 00582 } 00583 } 00584 00596 public static function maybeGetWebserverPrimaryGroup() { 00597 if ( !function_exists( 'posix_getegid' ) || !function_exists( 'posix_getpwuid' ) ) { 00598 # I don't know this, this isn't UNIX. 00599 return null; 00600 } 00601 00602 # posix_getegid() *not* getmygid() because we want the group of the webserver, 00603 # not whoever owns the current script. 00604 $gid = posix_getegid(); 00605 $getpwuid = posix_getpwuid( $gid ); 00606 $group = $getpwuid['name']; 00607 00608 return $group; 00609 } 00610 00627 public function parse( $text, $lineStart = false ) { 00628 global $wgParser; 00629 00630 try { 00631 $out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart ); 00632 $html = $out->getText(); 00633 } catch ( DBAccessError $e ) { 00634 $html = '<!--DB access attempted during parse--> ' . htmlspecialchars( $text ); 00635 00636 if ( !empty( $this->debug ) ) { 00637 $html .= "<!--\n" . $e->getTraceAsString() . "\n-->"; 00638 } 00639 } 00640 00641 return $html; 00642 } 00643 00647 public function getParserOptions() { 00648 return $this->parserOptions; 00649 } 00650 00651 public function disableLinkPopups() { 00652 $this->parserOptions->setExternalLinkTarget( false ); 00653 } 00654 00655 public function restoreLinkPopups() { 00656 global $wgExternalLinkTarget; 00657 $this->parserOptions->setExternalLinkTarget( $wgExternalLinkTarget ); 00658 } 00659 00668 public function populateSiteStats( DatabaseInstaller $installer ) { 00669 $status = $installer->getConnection(); 00670 if ( !$status->isOK() ) { 00671 return $status; 00672 } 00673 $status->value->insert( 00674 'site_stats', 00675 array( 00676 'ss_row_id' => 1, 00677 'ss_total_views' => 0, 00678 'ss_total_edits' => 0, 00679 'ss_good_articles' => 0, 00680 'ss_total_pages' => 0, 00681 'ss_users' => 0, 00682 'ss_images' => 0 00683 ), 00684 __METHOD__, 'IGNORE' 00685 ); 00686 00687 return Status::newGood(); 00688 } 00689 00693 public function exportVars() { 00694 foreach ( $this->settings as $name => $value ) { 00695 if ( substr( $name, 0, 2 ) == 'wg' ) { 00696 $GLOBALS[$name] = $value; 00697 } 00698 } 00699 } 00700 00705 protected function envCheckDB() { 00706 global $wgLang; 00707 00708 $allNames = array(); 00709 00710 // Messages: config-type-mysql, config-type-postgres, config-type-oracle, 00711 // config-type-sqlite 00712 foreach ( self::getDBTypes() as $name ) { 00713 $allNames[] = wfMessage( "config-type-$name" )->text(); 00714 } 00715 00716 $databases = $this->getCompiledDBs(); 00717 00718 $databases = array_flip( $databases ); 00719 foreach ( array_keys( $databases ) as $db ) { 00720 $installer = $this->getDBInstaller( $db ); 00721 $status = $installer->checkPrerequisites(); 00722 if ( !$status->isGood() ) { 00723 $this->showStatusMessage( $status ); 00724 } 00725 if ( !$status->isOK() ) { 00726 unset( $databases[$db] ); 00727 } 00728 } 00729 $databases = array_flip( $databases ); 00730 if ( !$databases ) { 00731 $this->showError( 'config-no-db', $wgLang->commaList( $allNames ) ); 00732 00733 // @todo FIXME: This only works for the web installer! 00734 return false; 00735 } 00736 00737 return true; 00738 } 00739 00745 protected function envCheckRegisterGlobals() { 00746 if ( wfIniGetBool( 'register_globals' ) ) { 00747 $this->showMessage( 'config-register-globals-error' ); 00748 return false; 00749 } 00750 00751 return true; 00752 } 00753 00758 protected function envCheckBrokenXML() { 00759 $test = new PhpXmlBugTester(); 00760 if ( !$test->ok ) { 00761 $this->showError( 'config-brokenlibxml' ); 00762 00763 return false; 00764 } 00765 00766 return true; 00767 } 00768 00773 protected function envCheckMagicQuotes() { 00774 $status = true; 00775 foreach ( array( 'gpc', 'runtime', 'sybase' ) as $magicJunk ) { 00776 if ( wfIniGetBool( "magic_quotes_$magicJunk" ) ) { 00777 $this->showError( "config-magic-quotes-$magicJunk" ); 00778 $status = false; 00779 } 00780 } 00781 00782 return $status; 00783 } 00784 00789 protected function envCheckMbstring() { 00790 if ( wfIniGetBool( 'mbstring.func_overload' ) ) { 00791 $this->showError( 'config-mbstring' ); 00792 00793 return false; 00794 } 00795 00796 return true; 00797 } 00798 00803 protected function envCheckSafeMode() { 00804 if ( wfIniGetBool( 'safe_mode' ) ) { 00805 $this->setVar( '_SafeMode', true ); 00806 $this->showMessage( 'config-safe-mode' ); 00807 } 00808 00809 return true; 00810 } 00811 00816 protected function envCheckXML() { 00817 if ( !function_exists( "utf8_encode" ) ) { 00818 $this->showError( 'config-xml-bad' ); 00819 00820 return false; 00821 } 00822 00823 return true; 00824 } 00825 00834 protected function envCheckPCRE() { 00835 wfSuppressWarnings(); 00836 $regexd = preg_replace( '/[\x{0430}-\x{04FF}]/iu', '', '-АБВГД-' ); 00837 // Need to check for \p support too, as PCRE can be compiled 00838 // with utf8 support, but not unicode property support. 00839 // check that \p{Zs} (space separators) matches 00840 // U+3000 (Ideographic space) 00841 $regexprop = preg_replace( '/\p{Zs}/u', '', "-\xE3\x80\x80-" ); 00842 wfRestoreWarnings(); 00843 if ( $regexd != '--' || $regexprop != '--' ) { 00844 $this->showError( 'config-pcre-no-utf8' ); 00845 00846 return false; 00847 } 00848 00849 return true; 00850 } 00851 00856 protected function envCheckMemory() { 00857 $limit = ini_get( 'memory_limit' ); 00858 00859 if ( !$limit || $limit == -1 ) { 00860 return true; 00861 } 00862 00863 $n = wfShorthandToInteger( $limit ); 00864 00865 if ( $n < $this->minMemorySize * 1024 * 1024 ) { 00866 $newLimit = "{$this->minMemorySize}M"; 00867 00868 if ( ini_set( "memory_limit", $newLimit ) === false ) { 00869 $this->showMessage( 'config-memory-bad', $limit ); 00870 } else { 00871 $this->showMessage( 'config-memory-raised', $limit, $newLimit ); 00872 $this->setVar( '_RaiseMemory', true ); 00873 } 00874 } 00875 00876 return true; 00877 } 00878 00882 protected function envCheckCache() { 00883 $caches = array(); 00884 foreach ( $this->objectCaches as $name => $function ) { 00885 if ( function_exists( $function ) ) { 00886 if ( $name == 'xcache' && !wfIniGetBool( 'xcache.var_size' ) ) { 00887 continue; 00888 } 00889 $caches[$name] = true; 00890 } 00891 } 00892 00893 if ( !$caches ) { 00894 $this->showMessage( 'config-no-cache' ); 00895 } 00896 00897 $this->setVar( '_Caches', $caches ); 00898 } 00899 00904 protected function envCheckModSecurity() { 00905 if ( self::apacheModulePresent( 'mod_security' ) ) { 00906 $this->showMessage( 'config-mod-security' ); 00907 } 00908 00909 return true; 00910 } 00911 00916 protected function envCheckDiff3() { 00917 $names = array( "gdiff3", "diff3", "diff3.exe" ); 00918 $versionInfo = array( '$1 --version 2>&1', 'GNU diffutils' ); 00919 00920 $diff3 = self::locateExecutableInDefaultPaths( $names, $versionInfo ); 00921 00922 if ( $diff3 ) { 00923 $this->setVar( 'wgDiff3', $diff3 ); 00924 } else { 00925 $this->setVar( 'wgDiff3', false ); 00926 $this->showMessage( 'config-diff3-bad' ); 00927 } 00928 00929 return true; 00930 } 00931 00936 protected function envCheckGraphics() { 00937 $names = array( wfIsWindows() ? 'convert.exe' : 'convert' ); 00938 $versionInfo = array( '$1 -version', 'ImageMagick' ); 00939 $convert = self::locateExecutableInDefaultPaths( $names, $versionInfo ); 00940 00941 $this->setVar( 'wgImageMagickConvertCommand', '' ); 00942 if ( $convert ) { 00943 $this->setVar( 'wgImageMagickConvertCommand', $convert ); 00944 $this->showMessage( 'config-imagemagick', $convert ); 00945 00946 return true; 00947 } elseif ( function_exists( 'imagejpeg' ) ) { 00948 $this->showMessage( 'config-gd' ); 00949 } else { 00950 $this->showMessage( 'config-no-scaling' ); 00951 } 00952 00953 return true; 00954 } 00955 00962 protected function envCheckGit() { 00963 $names = array( wfIsWindows() ? 'git.exe' : 'git' ); 00964 $versionInfo = array( '$1 --version', 'git version' ); 00965 00966 $git = self::locateExecutableInDefaultPaths( $names, $versionInfo ); 00967 00968 if ( $git ) { 00969 $this->setVar( 'wgGitBin', $git ); 00970 $this->showMessage( 'config-git', $git ); 00971 } else { 00972 $this->setVar( 'wgGitBin', false ); 00973 $this->showMessage( 'config-git-bad' ); 00974 } 00975 00976 return true; 00977 } 00978 00984 protected function envCheckServer() { 00985 $server = $this->envGetDefaultServer(); 00986 if ( $server !== null ) { 00987 $this->showMessage( 'config-using-server', $server ); 00988 } 00989 return true; 00990 } 00991 00997 protected function envCheckPath() { 00998 $this->showMessage( 00999 'config-using-uri', 01000 $this->getVar( 'wgServer' ), 01001 $this->getVar( 'wgScriptPath' ) 01002 ); 01003 return true; 01004 } 01005 01010 protected function envCheckShellLocale() { 01011 $os = php_uname( 's' ); 01012 $supported = array( 'Linux', 'SunOS', 'HP-UX', 'Darwin' ); # Tested these 01013 01014 if ( !in_array( $os, $supported ) ) { 01015 return true; 01016 } 01017 01018 # Get a list of available locales. 01019 $ret = false; 01020 $lines = wfShellExec( '/usr/bin/locale -a', $ret ); 01021 01022 if ( $ret ) { 01023 return true; 01024 } 01025 01026 $lines = array_map( 'trim', explode( "\n", $lines ) ); 01027 $candidatesByLocale = array(); 01028 $candidatesByLang = array(); 01029 01030 foreach ( $lines as $line ) { 01031 if ( $line === '' ) { 01032 continue; 01033 } 01034 01035 if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) { 01036 continue; 01037 } 01038 01039 list( , $lang, , , ) = $m; 01040 01041 $candidatesByLocale[$m[0]] = $m; 01042 $candidatesByLang[$lang][] = $m; 01043 } 01044 01045 # Try the current value of LANG. 01046 if ( isset( $candidatesByLocale[getenv( 'LANG' )] ) ) { 01047 $this->setVar( 'wgShellLocale', getenv( 'LANG' ) ); 01048 01049 return true; 01050 } 01051 01052 # Try the most common ones. 01053 $commonLocales = array( 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' ); 01054 foreach ( $commonLocales as $commonLocale ) { 01055 if ( isset( $candidatesByLocale[$commonLocale] ) ) { 01056 $this->setVar( 'wgShellLocale', $commonLocale ); 01057 01058 return true; 01059 } 01060 } 01061 01062 # Is there an available locale in the Wiki's language? 01063 $wikiLang = $this->getVar( 'wgLanguageCode' ); 01064 01065 if ( isset( $candidatesByLang[$wikiLang] ) ) { 01066 $m = reset( $candidatesByLang[$wikiLang] ); 01067 $this->setVar( 'wgShellLocale', $m[0] ); 01068 01069 return true; 01070 } 01071 01072 # Are there any at all? 01073 if ( count( $candidatesByLocale ) ) { 01074 $m = reset( $candidatesByLocale ); 01075 $this->setVar( 'wgShellLocale', $m[0] ); 01076 01077 return true; 01078 } 01079 01080 # Give up. 01081 return true; 01082 } 01083 01088 protected function envCheckUploadsDirectory() { 01089 global $IP; 01090 01091 $dir = $IP . '/images/'; 01092 $url = $this->getVar( 'wgServer' ) . $this->getVar( 'wgScriptPath' ) . '/images/'; 01093 $safe = !$this->dirIsExecutable( $dir, $url ); 01094 01095 if ( !$safe ) { 01096 $this->showMessage( 'config-uploads-not-safe', $dir ); 01097 } 01098 01099 return true; 01100 } 01101 01107 protected function envCheckSuhosinMaxValueLength() { 01108 $maxValueLength = ini_get( 'suhosin.get.max_value_length' ); 01109 if ( $maxValueLength > 0 && $maxValueLength < 1024 ) { 01110 // Only warn if the value is below the sane 1024 01111 $this->showMessage( 'config-suhosin-max-value-length', $maxValueLength ); 01112 } 01113 01114 return true; 01115 } 01116 01122 protected function unicodeChar( $c ) { 01123 $c = hexdec( $c ); 01124 if ( $c <= 0x7F ) { 01125 return chr( $c ); 01126 } elseif ( $c <= 0x7FF ) { 01127 return chr( 0xC0 | $c >> 6 ) . chr( 0x80 | $c & 0x3F ); 01128 } elseif ( $c <= 0xFFFF ) { 01129 return chr( 0xE0 | $c >> 12 ) . chr( 0x80 | $c >> 6 & 0x3F ) 01130 . chr( 0x80 | $c & 0x3F ); 01131 } elseif ( $c <= 0x10FFFF ) { 01132 return chr( 0xF0 | $c >> 18 ) . chr( 0x80 | $c >> 12 & 0x3F ) 01133 . chr( 0x80 | $c >> 6 & 0x3F ) 01134 . chr( 0x80 | $c & 0x3F ); 01135 } else { 01136 return false; 01137 } 01138 } 01139 01143 protected function envCheckLibicu() { 01144 $utf8 = function_exists( 'utf8_normalize' ); 01145 $intl = function_exists( 'normalizer_normalize' ); 01146 01154 $not_normal_c = $this->unicodeChar( "FA6C" ); 01155 $normal_c = $this->unicodeChar( "242EE" ); 01156 01157 $useNormalizer = 'php'; 01158 $needsUpdate = false; 01159 01164 if ( $utf8 ) { 01165 $useNormalizer = 'utf8'; 01166 $utf8 = utf8_normalize( $not_normal_c, UtfNormal::UNORM_NFC ); 01167 if ( $utf8 !== $normal_c ) { 01168 $needsUpdate = true; 01169 } 01170 } 01171 if ( $intl ) { 01172 $useNormalizer = 'intl'; 01173 $intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C ); 01174 if ( $intl !== $normal_c ) { 01175 $needsUpdate = true; 01176 } 01177 } 01178 01179 // Uses messages 'config-unicode-using-php', 'config-unicode-using-utf8', 01180 // 'config-unicode-using-intl' 01181 if ( $useNormalizer === 'php' ) { 01182 $this->showMessage( 'config-unicode-pure-php-warning' ); 01183 } else { 01184 $this->showMessage( 'config-unicode-using-' . $useNormalizer ); 01185 if ( $needsUpdate ) { 01186 $this->showMessage( 'config-unicode-update-warning' ); 01187 } 01188 } 01189 } 01190 01194 protected function envCheckCtype() { 01195 if ( !function_exists( 'ctype_digit' ) ) { 01196 $this->showError( 'config-ctype' ); 01197 01198 return false; 01199 } 01200 01201 return true; 01202 } 01203 01207 protected function envCheckIconv() { 01208 if ( !function_exists( 'iconv' ) ) { 01209 $this->showError( 'config-iconv' ); 01210 01211 return false; 01212 } 01213 01214 return true; 01215 } 01216 01220 protected function envCheckJSON() { 01221 if ( !function_exists( 'json_decode' ) ) { 01222 $this->showError( 'config-json' ); 01223 01224 return false; 01225 } 01226 01227 return true; 01228 } 01229 01233 protected function envPrepServer() { 01234 $server = $this->envGetDefaultServer(); 01235 if ( $server !== null ) { 01236 $this->setVar( 'wgServer', $server ); 01237 } 01238 } 01239 01244 abstract protected function envGetDefaultServer(); 01245 01249 protected function envPrepExtension() { 01250 // @todo FIXME: Detect this properly 01251 if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) { 01252 $ext = '.php5'; 01253 } else { 01254 $ext = '.php'; 01255 } 01256 $this->setVar( 'wgScriptExtension', $ext ); 01257 } 01258 01262 protected function envPrepPath() { 01263 global $IP; 01264 $IP = dirname( dirname( __DIR__ ) ); 01265 $this->setVar( 'IP', $IP ); 01266 } 01267 01275 protected static function getPossibleBinPaths() { 01276 return array_merge( 01277 array( '/usr/bin', '/usr/local/bin', '/opt/csw/bin', 01278 '/usr/gnu/bin', '/usr/sfw/bin', '/sw/bin', '/opt/local/bin' ), 01279 explode( PATH_SEPARATOR, getenv( 'PATH' ) ) 01280 ); 01281 } 01282 01300 public static function locateExecutable( $path, $names, $versionInfo = false ) { 01301 if ( !is_array( $names ) ) { 01302 $names = array( $names ); 01303 } 01304 01305 foreach ( $names as $name ) { 01306 $command = $path . DIRECTORY_SEPARATOR . $name; 01307 01308 wfSuppressWarnings(); 01309 $file_exists = file_exists( $command ); 01310 wfRestoreWarnings(); 01311 01312 if ( $file_exists ) { 01313 if ( !$versionInfo ) { 01314 return $command; 01315 } 01316 01317 $file = str_replace( '$1', wfEscapeShellArg( $command ), $versionInfo[0] ); 01318 if ( strstr( wfShellExec( $file ), $versionInfo[1] ) !== false ) { 01319 return $command; 01320 } 01321 } 01322 } 01323 01324 return false; 01325 } 01326 01339 public static function locateExecutableInDefaultPaths( $names, $versionInfo = false ) { 01340 foreach ( self::getPossibleBinPaths() as $path ) { 01341 $exe = self::locateExecutable( $path, $names, $versionInfo ); 01342 if ( $exe !== false ) { 01343 return $exe; 01344 } 01345 } 01346 01347 return false; 01348 } 01349 01358 public function dirIsExecutable( $dir, $url ) { 01359 $scriptTypes = array( 01360 'php' => array( 01361 "<?php echo 'ex' . 'ec';", 01362 "#!/var/env php5\n<?php echo 'ex' . 'ec';", 01363 ), 01364 ); 01365 01366 // it would be good to check other popular languages here, but it'll be slow. 01367 01368 wfSuppressWarnings(); 01369 01370 foreach ( $scriptTypes as $ext => $contents ) { 01371 foreach ( $contents as $source ) { 01372 $file = 'exectest.' . $ext; 01373 01374 if ( !file_put_contents( $dir . $file, $source ) ) { 01375 break; 01376 } 01377 01378 try { 01379 $text = Http::get( $url . $file, array( 'timeout' => 3 ) ); 01380 } catch ( MWException $e ) { 01381 // Http::get throws with allow_url_fopen = false and no curl extension. 01382 $text = null; 01383 } 01384 unlink( $dir . $file ); 01385 01386 if ( $text == 'exec' ) { 01387 wfRestoreWarnings(); 01388 01389 return $ext; 01390 } 01391 } 01392 } 01393 01394 wfRestoreWarnings(); 01395 01396 return false; 01397 } 01398 01405 public static function apacheModulePresent( $moduleName ) { 01406 if ( function_exists( 'apache_get_modules' ) && in_array( $moduleName, apache_get_modules() ) ) { 01407 return true; 01408 } 01409 // try it the hard way 01410 ob_start(); 01411 phpinfo( INFO_MODULES ); 01412 $info = ob_get_clean(); 01413 01414 return strpos( $info, $moduleName ) !== false; 01415 } 01416 01422 public function setParserLanguage( $lang ) { 01423 $this->parserOptions->setTargetLanguage( $lang ); 01424 $this->parserOptions->setUserLang( $lang ); 01425 } 01426 01432 protected function getDocUrl( $page ) { 01433 return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page ); 01434 } 01435 01445 public function findExtensions( $directory = 'extensions' ) { 01446 if ( $this->getVar( 'IP' ) === null ) { 01447 return array(); 01448 } 01449 01450 $extDir = $this->getVar( 'IP' ) . '/' . $directory; 01451 if ( !is_readable( $extDir ) || !is_dir( $extDir ) ) { 01452 return array(); 01453 } 01454 01455 $dh = opendir( $extDir ); 01456 $exts = array(); 01457 while ( ( $file = readdir( $dh ) ) !== false ) { 01458 if ( !is_dir( "$extDir/$file" ) ) { 01459 continue; 01460 } 01461 if ( file_exists( "$extDir/$file/$file.php" ) ) { 01462 $exts[] = $file; 01463 } 01464 } 01465 closedir( $dh ); 01466 natcasesort( $exts ); 01467 01468 return $exts; 01469 } 01470 01478 public function getDefaultSkin( array $skinNames ) { 01479 $defaultSkin = $GLOBALS['wgDefaultSkin']; 01480 if ( in_array( $defaultSkin, $skinNames ) ) { 01481 return $defaultSkin; 01482 } else { 01483 return $skinNames[0]; 01484 } 01485 } 01486 01492 protected function includeExtensions() { 01493 global $IP; 01494 $exts = $this->getVar( '_Extensions' ); 01495 $IP = $this->getVar( 'IP' ); 01496 01505 global $wgAutoloadClasses; 01506 $wgAutoloadClasses = array(); 01507 01508 require "$IP/includes/DefaultSettings.php"; 01509 01510 foreach ( $exts as $e ) { 01511 require_once "$IP/extensions/$e/$e.php"; 01512 } 01513 01514 $hooksWeWant = isset( $wgHooks['LoadExtensionSchemaUpdates'] ) ? 01515 $wgHooks['LoadExtensionSchemaUpdates'] : array(); 01516 01517 // Unset everyone else's hooks. Lord knows what someone might be doing 01518 // in ParserFirstCallInit (see bug 27171) 01519 $GLOBALS['wgHooks'] = array( 'LoadExtensionSchemaUpdates' => $hooksWeWant ); 01520 01521 return Status::newGood(); 01522 } 01523 01536 protected function getInstallSteps( DatabaseInstaller $installer ) { 01537 $coreInstallSteps = array( 01538 array( 'name' => 'database', 'callback' => array( $installer, 'setupDatabase' ) ), 01539 array( 'name' => 'tables', 'callback' => array( $installer, 'createTables' ) ), 01540 array( 'name' => 'interwiki', 'callback' => array( $installer, 'populateInterwikiTable' ) ), 01541 array( 'name' => 'stats', 'callback' => array( $this, 'populateSiteStats' ) ), 01542 array( 'name' => 'keys', 'callback' => array( $this, 'generateKeys' ) ), 01543 array( 'name' => 'updates', 'callback' => array( $installer, 'insertUpdateKeys' ) ), 01544 array( 'name' => 'sysop', 'callback' => array( $this, 'createSysop' ) ), 01545 array( 'name' => 'mainpage', 'callback' => array( $this, 'createMainpage' ) ), 01546 ); 01547 01548 // Build the array of install steps starting from the core install list, 01549 // then adding any callbacks that wanted to attach after a given step 01550 foreach ( $coreInstallSteps as $step ) { 01551 $this->installSteps[] = $step; 01552 if ( isset( $this->extraInstallSteps[$step['name']] ) ) { 01553 $this->installSteps = array_merge( 01554 $this->installSteps, 01555 $this->extraInstallSteps[$step['name']] 01556 ); 01557 } 01558 } 01559 01560 // Prepend any steps that want to be at the beginning 01561 if ( isset( $this->extraInstallSteps['BEGINNING'] ) ) { 01562 $this->installSteps = array_merge( 01563 $this->extraInstallSteps['BEGINNING'], 01564 $this->installSteps 01565 ); 01566 } 01567 01568 // Extensions should always go first, chance to tie into hooks and such 01569 if ( count( $this->getVar( '_Extensions' ) ) ) { 01570 array_unshift( $this->installSteps, 01571 array( 'name' => 'extensions', 'callback' => array( $this, 'includeExtensions' ) ) 01572 ); 01573 $this->installSteps[] = array( 01574 'name' => 'extension-tables', 01575 'callback' => array( $installer, 'createExtensionTables' ) 01576 ); 01577 } 01578 01579 return $this->installSteps; 01580 } 01581 01590 public function performInstallation( $startCB, $endCB ) { 01591 $installResults = array(); 01592 $installer = $this->getDBInstaller(); 01593 $installer->preInstall(); 01594 $steps = $this->getInstallSteps( $installer ); 01595 foreach ( $steps as $stepObj ) { 01596 $name = $stepObj['name']; 01597 call_user_func_array( $startCB, array( $name ) ); 01598 01599 // Perform the callback step 01600 $status = call_user_func( $stepObj['callback'], $installer ); 01601 01602 // Output and save the results 01603 call_user_func( $endCB, $name, $status ); 01604 $installResults[$name] = $status; 01605 01606 // If we've hit some sort of fatal, we need to bail. 01607 // Callback already had a chance to do output above. 01608 if ( !$status->isOk() ) { 01609 break; 01610 } 01611 } 01612 if ( $status->isOk() ) { 01613 $this->setVar( '_InstallDone', true ); 01614 } 01615 01616 return $installResults; 01617 } 01618 01624 public function generateKeys() { 01625 $keys = array( 'wgSecretKey' => 64 ); 01626 if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) { 01627 $keys['wgUpgradeKey'] = 16; 01628 } 01629 01630 return $this->doGenerateKeys( $keys ); 01631 } 01632 01640 protected function doGenerateKeys( $keys ) { 01641 $status = Status::newGood(); 01642 01643 $strong = true; 01644 foreach ( $keys as $name => $length ) { 01645 $secretKey = MWCryptRand::generateHex( $length, true ); 01646 if ( !MWCryptRand::wasStrong() ) { 01647 $strong = false; 01648 } 01649 01650 $this->setVar( $name, $secretKey ); 01651 } 01652 01653 if ( !$strong ) { 01654 $names = array_keys( $keys ); 01655 $names = preg_replace( '/^(.*)$/', '\$$1', $names ); 01656 global $wgLang; 01657 $status->warning( 'config-insecure-keys', $wgLang->listToText( $names ), count( $names ) ); 01658 } 01659 01660 return $status; 01661 } 01662 01668 protected function createSysop() { 01669 $name = $this->getVar( '_AdminName' ); 01670 $user = User::newFromName( $name ); 01671 01672 if ( !$user ) { 01673 // We should've validated this earlier anyway! 01674 return Status::newFatal( 'config-admin-error-user', $name ); 01675 } 01676 01677 if ( $user->idForName() == 0 ) { 01678 $user->addToDatabase(); 01679 01680 try { 01681 $user->setPassword( $this->getVar( '_AdminPassword' ) ); 01682 } catch ( PasswordError $pwe ) { 01683 return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() ); 01684 } 01685 01686 $user->addGroup( 'sysop' ); 01687 $user->addGroup( 'bureaucrat' ); 01688 if ( $this->getVar( '_AdminEmail' ) ) { 01689 $user->setEmail( $this->getVar( '_AdminEmail' ) ); 01690 } 01691 $user->saveSettings(); 01692 01693 // Update user count 01694 $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 ); 01695 $ssUpdate->doUpdate(); 01696 } 01697 $status = Status::newGood(); 01698 01699 if ( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) { 01700 $this->subscribeToMediaWikiAnnounce( $status ); 01701 } 01702 01703 return $status; 01704 } 01705 01709 private function subscribeToMediaWikiAnnounce( Status $s ) { 01710 $params = array( 01711 'email' => $this->getVar( '_AdminEmail' ), 01712 'language' => 'en', 01713 'digest' => 0 01714 ); 01715 01716 // Mailman doesn't support as many languages as we do, so check to make 01717 // sure their selected language is available 01718 $myLang = $this->getVar( '_UserLang' ); 01719 if ( in_array( $myLang, $this->mediaWikiAnnounceLanguages ) ) { 01720 $myLang = $myLang == 'pt-br' ? 'pt_BR' : $myLang; // rewrite to Mailman's pt_BR 01721 $params['language'] = $myLang; 01722 } 01723 01724 if ( MWHttpRequest::canMakeRequests() ) { 01725 $res = MWHttpRequest::factory( $this->mediaWikiAnnounceUrl, 01726 array( 'method' => 'POST', 'postData' => $params ) )->execute(); 01727 if ( !$res->isOK() ) { 01728 $s->warning( 'config-install-subscribe-fail', $res->getMessage() ); 01729 } 01730 } else { 01731 $s->warning( 'config-install-subscribe-notpossible' ); 01732 } 01733 } 01734 01741 protected function createMainpage( DatabaseInstaller $installer ) { 01742 $status = Status::newGood(); 01743 try { 01744 $page = WikiPage::factory( Title::newMainPage() ); 01745 $content = new WikitextContent( 01746 wfMessage( 'mainpagetext' )->inContentLanguage()->text() . "\n\n" . 01747 wfMessage( 'mainpagedocfooter' )->inContentLanguage()->text() 01748 ); 01749 01750 $page->doEditContent( $content, 01751 '', 01752 EDIT_NEW, 01753 false, 01754 User::newFromName( 'MediaWiki default' ) 01755 ); 01756 } catch ( MWException $e ) { 01757 //using raw, because $wgShowExceptionDetails can not be set yet 01758 $status->fatal( 'config-install-mainpage-failed', $e->getMessage() ); 01759 } 01760 01761 return $status; 01762 } 01763 01767 public static function overrideConfig() { 01768 define( 'MW_NO_SESSION', 1 ); 01769 01770 // Don't access the database 01771 $GLOBALS['wgUseDatabaseMessages'] = false; 01772 // Don't cache langconv tables 01773 $GLOBALS['wgLanguageConverterCacheType'] = CACHE_NONE; 01774 // Debug-friendly 01775 $GLOBALS['wgShowExceptionDetails'] = true; 01776 // Don't break forms 01777 $GLOBALS['wgExternalLinkTarget'] = '_blank'; 01778 01779 // Extended debugging 01780 $GLOBALS['wgShowSQLErrors'] = true; 01781 $GLOBALS['wgShowDBErrorBacktrace'] = true; 01782 01783 // Allow multiple ob_flush() calls 01784 $GLOBALS['wgDisableOutputCompression'] = true; 01785 01786 // Use a sensible cookie prefix (not my_wiki) 01787 $GLOBALS['wgCookiePrefix'] = 'mw_installer'; 01788 01789 // Some of the environment checks make shell requests, remove limits 01790 $GLOBALS['wgMaxShellMemory'] = 0; 01791 } 01792 01800 public function addInstallStep( $callback, $findStep = 'BEGINNING' ) { 01801 $this->extraInstallSteps[$findStep][] = $callback; 01802 } 01803 01808 protected function disableTimeLimit() { 01809 wfSuppressWarnings(); 01810 set_time_limit( 0 ); 01811 wfRestoreWarnings(); 01812 } 01813 }