MediaWiki
REL1_22
|
00001 <?php 00039 abstract class Installer { 00040 00041 // This is the absolute minimum PHP version we can support 00042 const MINIMUM_PHP_VERSION = '5.3.2'; 00043 00050 const MINIMUM_PCRE_VERSION = '7.2'; 00051 00055 protected $settings; 00056 00062 protected $compiledDBs; 00063 00069 protected $dbInstallers = array(); 00070 00076 protected $minMemorySize = 50; 00077 00083 protected $parserTitle; 00084 00090 protected $parserOptions; 00091 00101 protected static $dbTypes = array( 00102 'mysql', 00103 'postgres', 00104 'oracle', 00105 'sqlite', 00106 ); 00107 00115 protected $envChecks = array( 00116 'envCheckDB', 00117 'envCheckRegisterGlobals', 00118 'envCheckBrokenXML', 00119 'envCheckPHP531', 00120 'envCheckMagicQuotes', 00121 'envCheckMagicSybase', 00122 'envCheckMbstring', 00123 'envCheckZE1', 00124 'envCheckSafeMode', 00125 'envCheckXML', 00126 'envCheckPCRE', 00127 'envCheckMemory', 00128 'envCheckCache', 00129 'envCheckModSecurity', 00130 'envCheckDiff3', 00131 'envCheckGraphics', 00132 'envCheckGit', 00133 'envCheckServer', 00134 'envCheckPath', 00135 'envCheckExtension', 00136 'envCheckShellLocale', 00137 'envCheckUploadsDirectory', 00138 'envCheckLibicu', 00139 'envCheckSuhosinMaxValueLength', 00140 'envCheckCtype', 00141 'envCheckJSON', 00142 ); 00143 00151 protected $defaultVarNames = array( 00152 'wgSitename', 00153 'wgPasswordSender', 00154 'wgLanguageCode', 00155 'wgRightsIcon', 00156 'wgRightsText', 00157 'wgRightsUrl', 00158 'wgMainCacheType', 00159 'wgEnableEmail', 00160 'wgEnableUserEmail', 00161 'wgEnotifUserTalk', 00162 'wgEnotifWatchlist', 00163 'wgEmailAuthentication', 00164 'wgDBtype', 00165 'wgDiff3', 00166 'wgImageMagickConvertCommand', 00167 'wgGitBin', 00168 'IP', 00169 'wgScriptPath', 00170 'wgScriptExtension', 00171 'wgMetaNamespace', 00172 'wgDeletedDirectory', 00173 'wgEnableUploads', 00174 'wgLogo', 00175 'wgShellLocale', 00176 'wgSecretKey', 00177 'wgUseInstantCommons', 00178 'wgUpgradeKey', 00179 'wgDefaultSkin', 00180 'wgResourceLoaderMaxQueryLength', 00181 ); 00182 00190 protected $internalDefaults = array( 00191 '_UserLang' => 'en', 00192 '_Environment' => false, 00193 '_SafeMode' => false, 00194 '_RaiseMemory' => false, 00195 '_UpgradeDone' => false, 00196 '_InstallDone' => false, 00197 '_Caches' => array(), 00198 '_InstallPassword' => '', 00199 '_SameAccount' => true, 00200 '_CreateDBAccount' => false, 00201 '_NamespaceType' => 'site-name', 00202 '_AdminName' => '', // will be set later, when the user selects language 00203 '_AdminPassword' => '', 00204 '_AdminPassword2' => '', 00205 '_AdminEmail' => '', 00206 '_Subscribe' => false, 00207 '_SkipOptional' => 'continue', 00208 '_RightsProfile' => 'wiki', 00209 '_LicenseCode' => 'none', 00210 '_CCDone' => false, 00211 '_Extensions' => array(), 00212 '_MemCachedServers' => '', 00213 '_UpgradeKeySupplied' => false, 00214 '_ExistingDBSettings' => false, 00215 ); 00216 00222 private $installSteps = array(); 00223 00229 protected $extraInstallSteps = array(); 00230 00236 protected $objectCaches = array( 00237 'xcache' => 'xcache_get', 00238 'apc' => 'apc_fetch', 00239 'wincache' => 'wincache_ucache_get' 00240 ); 00241 00247 public $rightsProfiles = array( 00248 'wiki' => array(), 00249 'no-anon' => array( 00250 '*' => array( 'edit' => false ) 00251 ), 00252 'fishbowl' => array( 00253 '*' => array( 00254 'createaccount' => false, 00255 'edit' => false, 00256 ), 00257 ), 00258 'private' => array( 00259 '*' => array( 00260 'createaccount' => false, 00261 'edit' => false, 00262 'read' => false, 00263 ), 00264 ), 00265 ); 00266 00272 public $licenses = array( 00273 'cc-by' => array( 00274 'url' => 'http://creativecommons.org/licenses/by/3.0/', 00275 'icon' => '{$wgStylePath}/common/images/cc-by.png', 00276 ), 00277 'cc-by-sa' => array( 00278 'url' => 'http://creativecommons.org/licenses/by-sa/3.0/', 00279 'icon' => '{$wgStylePath}/common/images/cc-by-sa.png', 00280 ), 00281 'cc-by-nc-sa' => array( 00282 'url' => 'http://creativecommons.org/licenses/by-nc-sa/3.0/', 00283 'icon' => '{$wgStylePath}/common/images/cc-by-nc-sa.png', 00284 ), 00285 'cc-0' => array( 00286 'url' => 'https://creativecommons.org/publicdomain/zero/1.0/', 00287 'icon' => '{$wgStylePath}/common/images/cc-0.png', 00288 ), 00289 'pd' => array( 00290 'url' => '', 00291 'icon' => '{$wgStylePath}/common/images/public-domain.png', 00292 ), 00293 'gfdl' => array( 00294 'url' => 'http://www.gnu.org/copyleft/fdl.html', 00295 'icon' => '{$wgStylePath}/common/images/gnu-fdl.png', 00296 ), 00297 'none' => array( 00298 'url' => '', 00299 'icon' => '', 00300 'text' => '' 00301 ), 00302 'cc-choose' => array( 00303 // Details will be filled in by the selector. 00304 'url' => '', 00305 'icon' => '', 00306 'text' => '', 00307 ), 00308 ); 00309 00313 protected $mediaWikiAnnounceUrl = 00314 'https://lists.wikimedia.org/mailman/subscribe/mediawiki-announce'; 00315 00319 protected $mediaWikiAnnounceLanguages = array( 00320 'ca', 'cs', 'da', 'de', 'en', 'es', 'et', 'eu', 'fi', 'fr', 'hr', 'hu', 00321 'it', 'ja', 'ko', 'lt', 'nl', 'no', 'pl', 'pt', 'pt-br', 'ro', 'ru', 00322 'sl', 'sr', 'sv', 'tr', 'uk' 00323 ); 00324 00332 abstract public function showMessage( $msg /*, ... */ ); 00333 00338 abstract public function showError( $msg /*, ... */ ); 00339 00344 abstract public function showStatusMessage( Status $status ); 00345 00349 public function __construct() { 00350 global $wgExtensionMessagesFiles, $wgUser; 00351 00352 // Disable the i18n cache and LoadBalancer 00353 Language::getLocalisationCache()->disableBackend(); 00354 LBFactory::disableBackend(); 00355 00356 // Load the installer's i18n file. 00357 $wgExtensionMessagesFiles['MediawikiInstaller'] = 00358 __DIR__ . '/Installer.i18n.php'; 00359 00360 // Having a user with id = 0 safeguards us from DB access via User::loadOptions(). 00361 $wgUser = User::newFromId( 0 ); 00362 00363 $this->settings = $this->internalDefaults; 00364 00365 foreach ( $this->defaultVarNames as $var ) { 00366 $this->settings[$var] = $GLOBALS[$var]; 00367 } 00368 00369 $compiledDBs = array(); 00370 foreach ( self::getDBTypes() as $type ) { 00371 $installer = $this->getDBInstaller( $type ); 00372 00373 if ( !$installer->isCompiled() ) { 00374 continue; 00375 } 00376 $compiledDBs[] = $type; 00377 00378 $defaults = $installer->getGlobalDefaults(); 00379 00380 foreach ( $installer->getGlobalNames() as $var ) { 00381 if ( isset( $defaults[$var] ) ) { 00382 $this->settings[$var] = $defaults[$var]; 00383 } else { 00384 $this->settings[$var] = $GLOBALS[$var]; 00385 } 00386 } 00387 } 00388 $this->compiledDBs = $compiledDBs; 00389 00390 $this->parserTitle = Title::newFromText( 'Installer' ); 00391 $this->parserOptions = new ParserOptions; // language will be wrong :( 00392 $this->parserOptions->setEditSection( false ); 00393 } 00394 00400 public static function getDBTypes() { 00401 return self::$dbTypes; 00402 } 00403 00417 public function doEnvironmentChecks() { 00418 $phpVersion = phpversion(); 00419 if ( version_compare( $phpVersion, self::MINIMUM_PHP_VERSION, '>=' ) ) { 00420 $this->showMessage( 'config-env-php', $phpVersion ); 00421 $good = true; 00422 } else { 00423 $this->showMessage( 'config-env-php-toolow', $phpVersion, self::MINIMUM_PHP_VERSION ); 00424 $good = false; 00425 } 00426 00427 // Must go here because an old version of PCRE can prevent other checks from completing 00428 if ( $good ) { 00429 list( $pcreVersion ) = explode( ' ', PCRE_VERSION, 2 ); 00430 if ( version_compare( $pcreVersion, self::MINIMUM_PCRE_VERSION, '<' ) ) { 00431 $this->showError( 'config-pcre-old', self::MINIMUM_PCRE_VERSION, $pcreVersion ); 00432 $good = false; 00433 } 00434 } 00435 00436 if ( $good ) { 00437 foreach ( $this->envChecks as $check ) { 00438 $status = $this->$check(); 00439 if ( $status === false ) { 00440 $good = false; 00441 } 00442 } 00443 } 00444 00445 $this->setVar( '_Environment', $good ); 00446 00447 return $good ? Status::newGood() : Status::newFatal( 'config-env-bad' ); 00448 } 00449 00456 public function setVar( $name, $value ) { 00457 $this->settings[$name] = $value; 00458 } 00459 00470 public function getVar( $name, $default = null ) { 00471 if ( !isset( $this->settings[$name] ) ) { 00472 return $default; 00473 } else { 00474 return $this->settings[$name]; 00475 } 00476 } 00477 00483 public function getCompiledDBs() { 00484 return $this->compiledDBs; 00485 } 00486 00494 public function getDBInstaller( $type = false ) { 00495 if ( !$type ) { 00496 $type = $this->getVar( 'wgDBtype' ); 00497 } 00498 00499 $type = strtolower( $type ); 00500 00501 if ( !isset( $this->dbInstallers[$type] ) ) { 00502 $class = ucfirst( $type ) . 'Installer'; 00503 $this->dbInstallers[$type] = new $class( $this ); 00504 } 00505 00506 return $this->dbInstallers[$type]; 00507 } 00508 00515 public static function getExistingLocalSettings() { 00516 global $IP; 00517 00518 wfSuppressWarnings(); 00519 $_lsExists = file_exists( "$IP/LocalSettings.php" ); 00520 wfRestoreWarnings(); 00521 00522 if ( !$_lsExists ) { 00523 return false; 00524 } 00525 unset( $_lsExists ); 00526 00527 require "$IP/includes/DefaultSettings.php"; 00528 require "$IP/LocalSettings.php"; 00529 if ( file_exists( "$IP/AdminSettings.php" ) ) { 00530 require "$IP/AdminSettings.php"; 00531 } 00532 00533 return get_defined_vars(); 00534 } 00535 00545 public function getFakePassword( $realPassword ) { 00546 return str_repeat( '*', strlen( $realPassword ) ); 00547 } 00548 00556 public function setPassword( $name, $value ) { 00557 if ( !preg_match( '/^\*+$/', $value ) ) { 00558 $this->setVar( $name, $value ); 00559 } 00560 } 00561 00573 public static function maybeGetWebserverPrimaryGroup() { 00574 if ( !function_exists( 'posix_getegid' ) || !function_exists( 'posix_getpwuid' ) ) { 00575 # I don't know this, this isn't UNIX. 00576 return null; 00577 } 00578 00579 # posix_getegid() *not* getmygid() because we want the group of the webserver, 00580 # not whoever owns the current script. 00581 $gid = posix_getegid(); 00582 $getpwuid = posix_getpwuid( $gid ); 00583 $group = $getpwuid['name']; 00584 00585 return $group; 00586 } 00587 00604 public function parse( $text, $lineStart = false ) { 00605 global $wgParser; 00606 00607 try { 00608 $out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart ); 00609 $html = $out->getText(); 00610 } catch ( DBAccessError $e ) { 00611 $html = '<!--DB access attempted during parse--> ' . htmlspecialchars( $text ); 00612 00613 if ( !empty( $this->debug ) ) { 00614 $html .= "<!--\n" . $e->getTraceAsString() . "\n-->"; 00615 } 00616 } 00617 00618 return $html; 00619 } 00620 00624 public function getParserOptions() { 00625 return $this->parserOptions; 00626 } 00627 00628 public function disableLinkPopups() { 00629 $this->parserOptions->setExternalLinkTarget( false ); 00630 } 00631 00632 public function restoreLinkPopups() { 00633 global $wgExternalLinkTarget; 00634 $this->parserOptions->setExternalLinkTarget( $wgExternalLinkTarget ); 00635 } 00636 00645 public function populateSiteStats( DatabaseInstaller $installer ) { 00646 $status = $installer->getConnection(); 00647 if ( !$status->isOK() ) { 00648 return $status; 00649 } 00650 $status->value->insert( 'site_stats', array( 00651 'ss_row_id' => 1, 00652 'ss_total_views' => 0, 00653 'ss_total_edits' => 0, 00654 'ss_good_articles' => 0, 00655 'ss_total_pages' => 0, 00656 'ss_users' => 0, 00657 'ss_images' => 0 ), 00658 __METHOD__, 'IGNORE' ); 00659 00660 return Status::newGood(); 00661 } 00662 00666 public function exportVars() { 00667 foreach ( $this->settings as $name => $value ) { 00668 if ( substr( $name, 0, 2 ) == 'wg' ) { 00669 $GLOBALS[$name] = $value; 00670 } 00671 } 00672 } 00673 00678 protected function envCheckDB() { 00679 global $wgLang; 00680 00681 $allNames = array(); 00682 00683 // Messages: config-type-mysql, config-type-postgres, config-type-oracle, 00684 // config-type-sqlite 00685 foreach ( self::getDBTypes() as $name ) { 00686 $allNames[] = wfMessage( "config-type-$name" )->text(); 00687 } 00688 00689 $databases = $this->getCompiledDBs(); 00690 00691 $databases = array_flip( $databases ); 00692 foreach ( array_keys( $databases ) as $db ) { 00693 $installer = $this->getDBInstaller( $db ); 00694 $status = $installer->checkPrerequisites(); 00695 if ( !$status->isGood() ) { 00696 $this->showStatusMessage( $status ); 00697 } 00698 if ( !$status->isOK() ) { 00699 unset( $databases[$db] ); 00700 } 00701 } 00702 $databases = array_flip( $databases ); 00703 if ( !$databases ) { 00704 $this->showError( 'config-no-db', $wgLang->commaList( $allNames ) ); 00705 00706 // @todo FIXME: This only works for the web installer! 00707 return false; 00708 } 00709 00710 return true; 00711 } 00712 00716 protected function envCheckRegisterGlobals() { 00717 if ( wfIniGetBool( 'register_globals' ) ) { 00718 $this->showMessage( 'config-register-globals' ); 00719 } 00720 } 00721 00726 protected function envCheckBrokenXML() { 00727 $test = new PhpXmlBugTester(); 00728 if ( !$test->ok ) { 00729 $this->showError( 'config-brokenlibxml' ); 00730 00731 return false; 00732 } 00733 00734 return true; 00735 } 00736 00742 protected function envCheckPHP531() { 00743 $test = new PhpRefCallBugTester; 00744 $test->execute(); 00745 if ( !$test->ok ) { 00746 $this->showError( 'config-using531', phpversion() ); 00747 00748 return false; 00749 } 00750 00751 return true; 00752 } 00753 00758 protected function envCheckMagicQuotes() { 00759 if ( wfIniGetBool( "magic_quotes_runtime" ) ) { 00760 $this->showError( 'config-magic-quotes-runtime' ); 00761 00762 return false; 00763 } 00764 00765 return true; 00766 } 00767 00772 protected function envCheckMagicSybase() { 00773 if ( wfIniGetBool( 'magic_quotes_sybase' ) ) { 00774 $this->showError( 'config-magic-quotes-sybase' ); 00775 00776 return false; 00777 } 00778 00779 return true; 00780 } 00781 00786 protected function envCheckMbstring() { 00787 if ( wfIniGetBool( 'mbstring.func_overload' ) ) { 00788 $this->showError( 'config-mbstring' ); 00789 00790 return false; 00791 } 00792 00793 return true; 00794 } 00795 00800 protected function envCheckZE1() { 00801 if ( wfIniGetBool( 'zend.ze1_compatibility_mode' ) ) { 00802 $this->showError( 'config-ze1' ); 00803 00804 return false; 00805 } 00806 00807 return true; 00808 } 00809 00814 protected function envCheckSafeMode() { 00815 if ( wfIniGetBool( 'safe_mode' ) ) { 00816 $this->setVar( '_SafeMode', true ); 00817 $this->showMessage( 'config-safe-mode' ); 00818 } 00819 00820 return true; 00821 } 00822 00827 protected function envCheckXML() { 00828 if ( !function_exists( "utf8_encode" ) ) { 00829 $this->showError( 'config-xml-bad' ); 00830 00831 return false; 00832 } 00833 00834 return true; 00835 } 00836 00845 protected function envCheckPCRE() { 00846 wfSuppressWarnings(); 00847 $regexd = preg_replace( '/[\x{0430}-\x{04FF}]/iu', '', '-АБВГД-' ); 00848 // Need to check for \p support too, as PCRE can be compiled 00849 // with utf8 support, but not unicode property support. 00850 // check that \p{Zs} (space separators) matches 00851 // U+3000 (Ideographic space) 00852 $regexprop = preg_replace( '/\p{Zs}/u', '', "-\xE3\x80\x80-" ); 00853 wfRestoreWarnings(); 00854 if ( $regexd != '--' || $regexprop != '--' ) { 00855 $this->showError( 'config-pcre-no-utf8' ); 00856 00857 return false; 00858 } 00859 00860 return true; 00861 } 00862 00867 protected function envCheckMemory() { 00868 $limit = ini_get( 'memory_limit' ); 00869 00870 if ( !$limit || $limit == -1 ) { 00871 return true; 00872 } 00873 00874 $n = wfShorthandToInteger( $limit ); 00875 00876 if ( $n < $this->minMemorySize * 1024 * 1024 ) { 00877 $newLimit = "{$this->minMemorySize}M"; 00878 00879 if ( ini_set( "memory_limit", $newLimit ) === false ) { 00880 $this->showMessage( 'config-memory-bad', $limit ); 00881 } else { 00882 $this->showMessage( 'config-memory-raised', $limit, $newLimit ); 00883 $this->setVar( '_RaiseMemory', true ); 00884 } 00885 } 00886 00887 return true; 00888 } 00889 00893 protected function envCheckCache() { 00894 $caches = array(); 00895 foreach ( $this->objectCaches as $name => $function ) { 00896 if ( function_exists( $function ) ) { 00897 if ( $name == 'xcache' && !wfIniGetBool( 'xcache.var_size' ) ) { 00898 continue; 00899 } 00900 $caches[$name] = true; 00901 } 00902 } 00903 00904 if ( !$caches ) { 00905 $this->showMessage( 'config-no-cache' ); 00906 } 00907 00908 $this->setVar( '_Caches', $caches ); 00909 } 00910 00915 protected function envCheckModSecurity() { 00916 if ( self::apacheModulePresent( 'mod_security' ) ) { 00917 $this->showMessage( 'config-mod-security' ); 00918 } 00919 00920 return true; 00921 } 00922 00927 protected function envCheckDiff3() { 00928 $names = array( "gdiff3", "diff3", "diff3.exe" ); 00929 $versionInfo = array( '$1 --version 2>&1', 'GNU diffutils' ); 00930 00931 $diff3 = self::locateExecutableInDefaultPaths( $names, $versionInfo ); 00932 00933 if ( $diff3 ) { 00934 $this->setVar( 'wgDiff3', $diff3 ); 00935 } else { 00936 $this->setVar( 'wgDiff3', false ); 00937 $this->showMessage( 'config-diff3-bad' ); 00938 } 00939 00940 return true; 00941 } 00942 00947 protected function envCheckGraphics() { 00948 $names = array( wfIsWindows() ? 'convert.exe' : 'convert' ); 00949 $versionInfo = array( '$1 -version', 'ImageMagick' ); 00950 $convert = self::locateExecutableInDefaultPaths( $names, $versionInfo ); 00951 00952 $this->setVar( 'wgImageMagickConvertCommand', '' ); 00953 if ( $convert ) { 00954 $this->setVar( 'wgImageMagickConvertCommand', $convert ); 00955 $this->showMessage( 'config-imagemagick', $convert ); 00956 00957 return true; 00958 } elseif ( function_exists( 'imagejpeg' ) ) { 00959 $this->showMessage( 'config-gd' ); 00960 } else { 00961 $this->showMessage( 'config-no-scaling' ); 00962 } 00963 00964 return true; 00965 } 00966 00973 protected function envCheckGit() { 00974 $names = array( wfIsWindows() ? 'git.exe' : 'git' ); 00975 $versionInfo = array( '$1 --version', 'git version' ); 00976 00977 $git = self::locateExecutableInDefaultPaths( $names, $versionInfo ); 00978 00979 if ( $git ) { 00980 $this->setVar( 'wgGitBin', $git ); 00981 $this->showMessage( 'config-git', $git ); 00982 } else { 00983 $this->setVar( 'wgGitBin', false ); 00984 $this->showMessage( 'config-git-bad' ); 00985 } 00986 00987 return true; 00988 } 00989 00993 protected function envCheckServer() { 00994 $server = $this->envGetDefaultServer(); 00995 if ( $server !== null ) { 00996 $this->showMessage( 'config-using-server', $server ); 00997 $this->setVar( 'wgServer', $server ); 00998 } 00999 01000 return true; 01001 } 01002 01007 abstract protected function envGetDefaultServer(); 01008 01013 protected function envCheckPath() { 01014 global $IP; 01015 $IP = dirname( dirname( __DIR__ ) ); 01016 $this->setVar( 'IP', $IP ); 01017 01018 $this->showMessage( 01019 'config-using-uri', 01020 $this->getVar( 'wgServer' ), 01021 $this->getVar( 'wgScriptPath' ) 01022 ); 01023 01024 return true; 01025 } 01026 01031 protected function envCheckExtension() { 01032 // @todo FIXME: Detect this properly 01033 if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) { 01034 $ext = 'php5'; 01035 } else { 01036 $ext = 'php'; 01037 } 01038 $this->setVar( 'wgScriptExtension', ".$ext" ); 01039 01040 return true; 01041 } 01042 01047 protected function envCheckShellLocale() { 01048 $os = php_uname( 's' ); 01049 $supported = array( 'Linux', 'SunOS', 'HP-UX', 'Darwin' ); # Tested these 01050 01051 if ( !in_array( $os, $supported ) ) { 01052 return true; 01053 } 01054 01055 # Get a list of available locales. 01056 $ret = false; 01057 $lines = wfShellExec( '/usr/bin/locale -a', $ret ); 01058 01059 if ( $ret ) { 01060 return true; 01061 } 01062 01063 $lines = array_map( 'trim', explode( "\n", $lines ) ); 01064 $candidatesByLocale = array(); 01065 $candidatesByLang = array(); 01066 01067 foreach ( $lines as $line ) { 01068 if ( $line === '' ) { 01069 continue; 01070 } 01071 01072 if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) { 01073 continue; 01074 } 01075 01076 list( , $lang, , , ) = $m; 01077 01078 $candidatesByLocale[$m[0]] = $m; 01079 $candidatesByLang[$lang][] = $m; 01080 } 01081 01082 # Try the current value of LANG. 01083 if ( isset( $candidatesByLocale[getenv( 'LANG' )] ) ) { 01084 $this->setVar( 'wgShellLocale', getenv( 'LANG' ) ); 01085 01086 return true; 01087 } 01088 01089 # Try the most common ones. 01090 $commonLocales = array( 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' ); 01091 foreach ( $commonLocales as $commonLocale ) { 01092 if ( isset( $candidatesByLocale[$commonLocale] ) ) { 01093 $this->setVar( 'wgShellLocale', $commonLocale ); 01094 01095 return true; 01096 } 01097 } 01098 01099 # Is there an available locale in the Wiki's language? 01100 $wikiLang = $this->getVar( 'wgLanguageCode' ); 01101 01102 if ( isset( $candidatesByLang[$wikiLang] ) ) { 01103 $m = reset( $candidatesByLang[$wikiLang] ); 01104 $this->setVar( 'wgShellLocale', $m[0] ); 01105 01106 return true; 01107 } 01108 01109 # Are there any at all? 01110 if ( count( $candidatesByLocale ) ) { 01111 $m = reset( $candidatesByLocale ); 01112 $this->setVar( 'wgShellLocale', $m[0] ); 01113 01114 return true; 01115 } 01116 01117 # Give up. 01118 return true; 01119 } 01120 01125 protected function envCheckUploadsDirectory() { 01126 global $IP; 01127 01128 $dir = $IP . '/images/'; 01129 $url = $this->getVar( 'wgServer' ) . $this->getVar( 'wgScriptPath' ) . '/images/'; 01130 $safe = !$this->dirIsExecutable( $dir, $url ); 01131 01132 if ( !$safe ) { 01133 $this->showMessage( 'config-uploads-not-safe', $dir ); 01134 } 01135 01136 return true; 01137 } 01138 01144 protected function envCheckSuhosinMaxValueLength() { 01145 $maxValueLength = ini_get( 'suhosin.get.max_value_length' ); 01146 if ( $maxValueLength > 0 && $maxValueLength < 1024 ) { 01147 // Only warn if the value is below the sane 1024 01148 $this->showMessage( 'config-suhosin-max-value-length', $maxValueLength ); 01149 } 01150 01151 return true; 01152 } 01153 01159 protected function unicodeChar( $c ) { 01160 $c = hexdec( $c ); 01161 if ( $c <= 0x7F ) { 01162 return chr( $c ); 01163 } elseif ( $c <= 0x7FF ) { 01164 return chr( 0xC0 | $c >> 6 ) . chr( 0x80 | $c & 0x3F ); 01165 } elseif ( $c <= 0xFFFF ) { 01166 return chr( 0xE0 | $c >> 12 ) . chr( 0x80 | $c >> 6 & 0x3F ) 01167 . chr( 0x80 | $c & 0x3F ); 01168 } elseif ( $c <= 0x10FFFF ) { 01169 return chr( 0xF0 | $c >> 18 ) . chr( 0x80 | $c >> 12 & 0x3F ) 01170 . chr( 0x80 | $c >> 6 & 0x3F ) 01171 . chr( 0x80 | $c & 0x3F ); 01172 } else { 01173 return false; 01174 } 01175 } 01176 01180 protected function envCheckLibicu() { 01181 $utf8 = function_exists( 'utf8_normalize' ); 01182 $intl = function_exists( 'normalizer_normalize' ); 01183 01191 $not_normal_c = $this->unicodeChar( "FA6C" ); 01192 $normal_c = $this->unicodeChar( "242EE" ); 01193 01194 $useNormalizer = 'php'; 01195 $needsUpdate = false; 01196 01201 if ( $utf8 ) { 01202 $useNormalizer = 'utf8'; 01203 $utf8 = utf8_normalize( $not_normal_c, UtfNormal::UNORM_NFC ); 01204 if ( $utf8 !== $normal_c ) { 01205 $needsUpdate = true; 01206 } 01207 } 01208 if ( $intl ) { 01209 $useNormalizer = 'intl'; 01210 $intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C ); 01211 if ( $intl !== $normal_c ) { 01212 $needsUpdate = true; 01213 } 01214 } 01215 01216 // Uses messages 'config-unicode-using-php', 'config-unicode-using-utf8', 01217 // 'config-unicode-using-intl' 01218 if ( $useNormalizer === 'php' ) { 01219 $this->showMessage( 'config-unicode-pure-php-warning' ); 01220 } else { 01221 $this->showMessage( 'config-unicode-using-' . $useNormalizer ); 01222 if ( $needsUpdate ) { 01223 $this->showMessage( 'config-unicode-update-warning' ); 01224 } 01225 } 01226 } 01227 01231 protected function envCheckCtype() { 01232 if ( !function_exists( 'ctype_digit' ) ) { 01233 $this->showError( 'config-ctype' ); 01234 01235 return false; 01236 } 01237 01238 return true; 01239 } 01240 01244 protected function envCheckJSON() { 01245 if ( !function_exists( 'json_decode' ) ) { 01246 $this->showError( 'config-json' ); 01247 01248 return false; 01249 } 01250 01251 return true; 01252 } 01253 01261 protected static function getPossibleBinPaths() { 01262 return array_merge( 01263 array( '/usr/bin', '/usr/local/bin', '/opt/csw/bin', 01264 '/usr/gnu/bin', '/usr/sfw/bin', '/sw/bin', '/opt/local/bin' ), 01265 explode( PATH_SEPARATOR, getenv( 'PATH' ) ) 01266 ); 01267 } 01268 01286 public static function locateExecutable( $path, $names, $versionInfo = false ) { 01287 if ( !is_array( $names ) ) { 01288 $names = array( $names ); 01289 } 01290 01291 foreach ( $names as $name ) { 01292 $command = $path . DIRECTORY_SEPARATOR . $name; 01293 01294 wfSuppressWarnings(); 01295 $file_exists = file_exists( $command ); 01296 wfRestoreWarnings(); 01297 01298 if ( $file_exists ) { 01299 if ( !$versionInfo ) { 01300 return $command; 01301 } 01302 01303 $file = str_replace( '$1', wfEscapeShellArg( $command ), $versionInfo[0] ); 01304 if ( strstr( wfShellExec( $file ), $versionInfo[1] ) !== false ) { 01305 return $command; 01306 } 01307 } 01308 } 01309 01310 return false; 01311 } 01312 01320 public static function locateExecutableInDefaultPaths( $names, $versionInfo = false ) { 01321 foreach ( self::getPossibleBinPaths() as $path ) { 01322 $exe = self::locateExecutable( $path, $names, $versionInfo ); 01323 if ( $exe !== false ) { 01324 return $exe; 01325 } 01326 } 01327 01328 return false; 01329 } 01330 01339 public function dirIsExecutable( $dir, $url ) { 01340 $scriptTypes = array( 01341 'php' => array( 01342 "<?php echo 'ex' . 'ec';", 01343 "#!/var/env php5\n<?php echo 'ex' . 'ec';", 01344 ), 01345 ); 01346 01347 // it would be good to check other popular languages here, but it'll be slow. 01348 01349 wfSuppressWarnings(); 01350 01351 foreach ( $scriptTypes as $ext => $contents ) { 01352 foreach ( $contents as $source ) { 01353 $file = 'exectest.' . $ext; 01354 01355 if ( !file_put_contents( $dir . $file, $source ) ) { 01356 break; 01357 } 01358 01359 try { 01360 $text = Http::get( $url . $file, array( 'timeout' => 3 ) ); 01361 } catch ( MWException $e ) { 01362 // Http::get throws with allow_url_fopen = false and no curl extension. 01363 $text = null; 01364 } 01365 unlink( $dir . $file ); 01366 01367 if ( $text == 'exec' ) { 01368 wfRestoreWarnings(); 01369 01370 return $ext; 01371 } 01372 } 01373 } 01374 01375 wfRestoreWarnings(); 01376 01377 return false; 01378 } 01379 01386 public static function apacheModulePresent( $moduleName ) { 01387 if ( function_exists( 'apache_get_modules' ) && in_array( $moduleName, apache_get_modules() ) ) { 01388 return true; 01389 } 01390 // try it the hard way 01391 ob_start(); 01392 phpinfo( INFO_MODULES ); 01393 $info = ob_get_clean(); 01394 01395 return strpos( $info, $moduleName ) !== false; 01396 } 01397 01403 public function setParserLanguage( $lang ) { 01404 $this->parserOptions->setTargetLanguage( $lang ); 01405 $this->parserOptions->setUserLang( $lang ); 01406 } 01407 01413 protected function getDocUrl( $page ) { 01414 return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page ); 01415 } 01416 01421 public function getExtensionInfo( $file ) { 01422 global $wgExtensionCredits, $wgVersion, $wgResourceModules; 01423 01424 $wgVersion = "1.22"; 01425 $wgResourceModules = array(); 01426 require_once $file ; 01427 $e = array_values( $wgExtensionCredits ); 01428 if( $e ) { 01429 $ext = array_values( $e[0] ); 01430 $wgExtensionCredits = array(); 01431 return $ext[0]; 01432 } 01433 } 01434 01441 public function findExtensions() { 01442 if ( $this->getVar( 'IP' ) === null ) { 01443 return array(); 01444 } 01445 01446 $extDir = $this->getVar( 'IP' ) . '/extensions'; 01447 if ( !is_readable( $extDir ) || !is_dir( $extDir ) ) { 01448 return array(); 01449 } 01450 01451 $dh = opendir( $extDir ); 01452 $exts = array(); 01453 while ( ( $file = readdir( $dh ) ) !== false ) { 01454 if ( !is_dir( "$extDir/$file" ) ) { 01455 continue; 01456 } 01457 01458 $extFile = "$extDir/$file/$file.php"; 01459 $extI18NFile = "$extDir/$file/$file.i18n.php"; 01460 if ( file_exists( $extFile ) ) { 01461 if ( $info = $this->getExtensionInfo( $extFile ) ) { 01462 $exts[$info['name']] = $info; 01463 01464 if ( file_exists( $extI18NFile ) ) { 01465 global $wgExtensionMessagesFiles; 01466 $wgExtensionMessagesFiles[$file] = $extI18NFile; 01467 } 01468 } 01469 } 01470 } 01471 closedir( $dh ); 01472 uksort( $exts, 'strnatcasecmp' ); 01473 return $exts; 01474 } 01475 01481 protected function includeExtensions() { 01482 global $IP; 01483 $exts = $this->getVar( '_Extensions' ); 01484 $IP = $this->getVar( 'IP' ); 01485 01494 global $wgAutoloadClasses; 01495 $wgAutoloadClasses = array(); 01496 01497 require "$IP/includes/DefaultSettings.php"; 01498 01499 $extensions = $this->findExtensions(); 01500 foreach ( $exts as $e ) { 01501 require_once $extensions[$e]['path']; 01502 } 01503 01504 $hooksWeWant = isset( $wgHooks['LoadExtensionSchemaUpdates'] ) ? 01505 $wgHooks['LoadExtensionSchemaUpdates'] : array(); 01506 01507 // Unset everyone else's hooks. Lord knows what someone might be doing 01508 // in ParserFirstCallInit (see bug 27171) 01509 $GLOBALS['wgHooks'] = array( 'LoadExtensionSchemaUpdates' => $hooksWeWant ); 01510 01511 return Status::newGood(); 01512 } 01513 01526 protected function getInstallSteps( DatabaseInstaller $installer ) { 01527 $coreInstallSteps = array( 01528 array( 'name' => 'database', 'callback' => array( $installer, 'setupDatabase' ) ), 01529 array( 'name' => 'tables', 'callback' => array( $installer, 'createTables' ) ), 01530 array( 'name' => 'interwiki', 'callback' => array( $installer, 'populateInterwikiTable' ) ), 01531 array( 'name' => 'stats', 'callback' => array( $this, 'populateSiteStats' ) ), 01532 array( 'name' => 'keys', 'callback' => array( $this, 'generateKeys' ) ), 01533 array( 'name' => 'sysop', 'callback' => array( $this, 'createSysop' ) ), 01534 array( 'name' => 'mainpage', 'callback' => array( $this, 'createMainpage' ) ), 01535 ); 01536 01537 // Build the array of install steps starting from the core install list, 01538 // then adding any callbacks that wanted to attach after a given step 01539 foreach ( $coreInstallSteps as $step ) { 01540 $this->installSteps[] = $step; 01541 if ( isset( $this->extraInstallSteps[$step['name']] ) ) { 01542 $this->installSteps = array_merge( 01543 $this->installSteps, 01544 $this->extraInstallSteps[$step['name']] 01545 ); 01546 } 01547 } 01548 01549 // Prepend any steps that want to be at the beginning 01550 if ( isset( $this->extraInstallSteps['BEGINNING'] ) ) { 01551 $this->installSteps = array_merge( 01552 $this->extraInstallSteps['BEGINNING'], 01553 $this->installSteps 01554 ); 01555 } 01556 01557 // Extensions should always go first, chance to tie into hooks and such 01558 if ( count( $this->getVar( '_Extensions' ) ) ) { 01559 array_unshift( $this->installSteps, 01560 array( 'name' => 'extensions', 'callback' => array( $this, 'includeExtensions' ) ) 01561 ); 01562 $this->installSteps[] = array( 01563 'name' => 'extension-tables', 01564 'callback' => array( $installer, 'createExtensionTables' ) 01565 ); 01566 } 01567 01568 return $this->installSteps; 01569 } 01570 01579 public function performInstallation( $startCB, $endCB ) { 01580 $installResults = array(); 01581 $installer = $this->getDBInstaller(); 01582 $installer->preInstall(); 01583 $steps = $this->getInstallSteps( $installer ); 01584 foreach ( $steps as $stepObj ) { 01585 $name = $stepObj['name']; 01586 call_user_func_array( $startCB, array( $name ) ); 01587 01588 // Perform the callback step 01589 $status = call_user_func( $stepObj['callback'], $installer ); 01590 01591 // Output and save the results 01592 call_user_func( $endCB, $name, $status ); 01593 $installResults[$name] = $status; 01594 01595 // If we've hit some sort of fatal, we need to bail. 01596 // Callback already had a chance to do output above. 01597 if ( !$status->isOk() ) { 01598 break; 01599 } 01600 } 01601 if ( $status->isOk() ) { 01602 $this->setVar( '_InstallDone', true ); 01603 } 01604 01605 return $installResults; 01606 } 01607 01613 public function generateKeys() { 01614 $keys = array( 'wgSecretKey' => 64 ); 01615 if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) { 01616 $keys['wgUpgradeKey'] = 16; 01617 } 01618 01619 return $this->doGenerateKeys( $keys ); 01620 } 01621 01629 protected function doGenerateKeys( $keys ) { 01630 $status = Status::newGood(); 01631 01632 $strong = true; 01633 foreach ( $keys as $name => $length ) { 01634 $secretKey = MWCryptRand::generateHex( $length, true ); 01635 if ( !MWCryptRand::wasStrong() ) { 01636 $strong = false; 01637 } 01638 01639 $this->setVar( $name, $secretKey ); 01640 } 01641 01642 if ( !$strong ) { 01643 $names = array_keys( $keys ); 01644 $names = preg_replace( '/^(.*)$/', '\$$1', $names ); 01645 global $wgLang; 01646 $status->warning( 'config-insecure-keys', $wgLang->listToText( $names ), count( $names ) ); 01647 } 01648 01649 return $status; 01650 } 01651 01657 protected function createSysop() { 01658 $name = $this->getVar( '_AdminName' ); 01659 $user = User::newFromName( $name ); 01660 01661 if ( !$user ) { 01662 // We should've validated this earlier anyway! 01663 return Status::newFatal( 'config-admin-error-user', $name ); 01664 } 01665 01666 if ( $user->idForName() == 0 ) { 01667 $user->addToDatabase(); 01668 01669 try { 01670 $user->setPassword( $this->getVar( '_AdminPassword' ) ); 01671 } catch ( PasswordError $pwe ) { 01672 return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() ); 01673 } 01674 01675 $user->addGroup( 'sysop' ); 01676 $user->addGroup( 'bureaucrat' ); 01677 if ( $this->getVar( '_AdminEmail' ) ) { 01678 $user->setEmail( $this->getVar( '_AdminEmail' ) ); 01679 } 01680 $user->saveSettings(); 01681 01682 // Update user count 01683 $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 ); 01684 $ssUpdate->doUpdate(); 01685 } 01686 $status = Status::newGood(); 01687 01688 if ( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) { 01689 $this->subscribeToMediaWikiAnnounce( $status ); 01690 } 01691 01692 return $status; 01693 } 01694 01698 private function subscribeToMediaWikiAnnounce( Status $s ) { 01699 $params = array( 01700 'email' => $this->getVar( '_AdminEmail' ), 01701 'language' => 'en', 01702 'digest' => 0 01703 ); 01704 01705 // Mailman doesn't support as many languages as we do, so check to make 01706 // sure their selected language is available 01707 $myLang = $this->getVar( '_UserLang' ); 01708 if ( in_array( $myLang, $this->mediaWikiAnnounceLanguages ) ) { 01709 $myLang = $myLang == 'pt-br' ? 'pt_BR' : $myLang; // rewrite to Mailman's pt_BR 01710 $params['language'] = $myLang; 01711 } 01712 01713 if ( MWHttpRequest::canMakeRequests() ) { 01714 $res = MWHttpRequest::factory( $this->mediaWikiAnnounceUrl, 01715 array( 'method' => 'POST', 'postData' => $params ) )->execute(); 01716 if ( !$res->isOK() ) { 01717 $s->warning( 'config-install-subscribe-fail', $res->getMessage() ); 01718 } 01719 } else { 01720 $s->warning( 'config-install-subscribe-notpossible' ); 01721 } 01722 } 01723 01730 protected function createMainpage( DatabaseInstaller $installer ) { 01731 $status = Status::newGood(); 01732 try { 01733 $page = WikiPage::factory( Title::newMainPage() ); 01734 $content = new WikitextContent( 01735 wfMessage( 'mainpagetext' )->inContentLanguage()->text() . "\n\n" . 01736 wfMessage( 'mainpagedocfooter' )->inContentLanguage()->text() 01737 ); 01738 01739 $page->doEditContent( $content, 01740 '', 01741 EDIT_NEW, 01742 false, 01743 User::newFromName( 'MediaWiki default' ) 01744 ); 01745 } catch ( MWException $e ) { 01746 //using raw, because $wgShowExceptionDetails can not be set yet 01747 $status->fatal( 'config-install-mainpage-failed', $e->getMessage() ); 01748 } 01749 01750 return $status; 01751 } 01752 01756 public static function overrideConfig() { 01757 define( 'MW_NO_SESSION', 1 ); 01758 01759 // Don't access the database 01760 $GLOBALS['wgUseDatabaseMessages'] = false; 01761 // Don't cache langconv tables 01762 $GLOBALS['wgLanguageConverterCacheType'] = CACHE_NONE; 01763 // Debug-friendly 01764 $GLOBALS['wgShowExceptionDetails'] = true; 01765 // Don't break forms 01766 $GLOBALS['wgExternalLinkTarget'] = '_blank'; 01767 01768 // Extended debugging 01769 $GLOBALS['wgShowSQLErrors'] = true; 01770 $GLOBALS['wgShowDBErrorBacktrace'] = true; 01771 01772 // Allow multiple ob_flush() calls 01773 $GLOBALS['wgDisableOutputCompression'] = true; 01774 01775 // Use a sensible cookie prefix (not my_wiki) 01776 $GLOBALS['wgCookiePrefix'] = 'mw_installer'; 01777 01778 // Some of the environment checks make shell requests, remove limits 01779 $GLOBALS['wgMaxShellMemory'] = 0; 01780 01781 // Don't bother embedding images into generated CSS, which is not cached 01782 $GLOBALS['wgResourceLoaderLESSFunctions']['embeddable'] = function( $frame, $less ) { 01783 return $less->toBool( false ); 01784 }; 01785 } 01786 01794 public function addInstallStep( $callback, $findStep = 'BEGINNING' ) { 01795 $this->extraInstallSteps[$findStep][] = $callback; 01796 } 01797 01802 protected function disableTimeLimit() { 01803 wfSuppressWarnings(); 01804 set_time_limit( 0 ); 01805 wfRestoreWarnings(); 01806 } 01807 }