MediaWiki
REL1_19
|
00001 <?php 00024 abstract class Installer { 00025 00026 // This is the absolute minimum PHP version we can support 00027 const MINIMUM_PHP_VERSION = '5.2.3'; 00028 00032 protected $settings; 00033 00039 protected $dbInstallers = array(); 00040 00046 protected $minMemorySize = 50; 00047 00053 protected $parserTitle; 00054 00060 protected $parserOptions; 00061 00071 protected static $dbTypes = array( 00072 'mysql', 00073 'postgres', 00074 'oracle', 00075 'sqlite', 00076 'ibm_db2', 00077 ); 00078 00086 protected $envChecks = array( 00087 'envCheckDB', 00088 'envCheckRegisterGlobals', 00089 'envCheckBrokenXML', 00090 'envCheckPHP531', 00091 'envCheckMagicQuotes', 00092 'envCheckMagicSybase', 00093 'envCheckMbstring', 00094 'envCheckZE1', 00095 'envCheckSafeMode', 00096 'envCheckXML', 00097 'envCheckPCRE', 00098 'envCheckMemory', 00099 'envCheckCache', 00100 'envCheckModSecurity', 00101 'envCheckDiff3', 00102 'envCheckGraphics', 00103 'envCheckServer', 00104 'envCheckPath', 00105 'envCheckExtension', 00106 'envCheckShellLocale', 00107 'envCheckUploadsDirectory', 00108 'envCheckLibicu', 00109 'envCheckSuhosinMaxValueLength', 00110 'envCheckCtype', 00111 ); 00112 00120 protected $defaultVarNames = array( 00121 'wgSitename', 00122 'wgPasswordSender', 00123 'wgLanguageCode', 00124 'wgRightsIcon', 00125 'wgRightsText', 00126 'wgRightsUrl', 00127 'wgMainCacheType', 00128 'wgEnableEmail', 00129 'wgEnableUserEmail', 00130 'wgEnotifUserTalk', 00131 'wgEnotifWatchlist', 00132 'wgEmailAuthentication', 00133 'wgDBtype', 00134 'wgDiff3', 00135 'wgImageMagickConvertCommand', 00136 'IP', 00137 'wgServer', 00138 'wgScriptPath', 00139 'wgScriptExtension', 00140 'wgMetaNamespace', 00141 'wgDeletedDirectory', 00142 'wgEnableUploads', 00143 'wgLogo', 00144 'wgShellLocale', 00145 'wgSecretKey', 00146 'wgUseInstantCommons', 00147 'wgUpgradeKey', 00148 'wgDefaultSkin', 00149 'wgResourceLoaderMaxQueryLength', 00150 ); 00151 00159 protected $internalDefaults = array( 00160 '_UserLang' => 'en', 00161 '_Environment' => false, 00162 '_CompiledDBs' => array(), 00163 '_SafeMode' => false, 00164 '_RaiseMemory' => false, 00165 '_UpgradeDone' => false, 00166 '_InstallDone' => false, 00167 '_Caches' => array(), 00168 '_InstallPassword' => '', 00169 '_SameAccount' => true, 00170 '_CreateDBAccount' => false, 00171 '_NamespaceType' => 'site-name', 00172 '_AdminName' => '', // will be set later, when the user selects language 00173 '_AdminPassword' => '', 00174 '_AdminPassword2' => '', 00175 '_AdminEmail' => '', 00176 '_Subscribe' => false, 00177 '_SkipOptional' => 'continue', 00178 '_RightsProfile' => 'wiki', 00179 '_LicenseCode' => 'none', 00180 '_CCDone' => false, 00181 '_Extensions' => array(), 00182 '_MemCachedServers' => '', 00183 '_UpgradeKeySupplied' => false, 00184 '_ExistingDBSettings' => false, 00185 ); 00186 00192 private $installSteps = array(); 00193 00199 protected $extraInstallSteps = array(); 00200 00206 protected $objectCaches = array( 00207 'xcache' => 'xcache_get', 00208 'apc' => 'apc_fetch', 00209 'wincache' => 'wincache_ucache_get' 00210 ); 00211 00217 public $rightsProfiles = array( 00218 'wiki' => array(), 00219 'no-anon' => array( 00220 '*' => array( 'edit' => false ) 00221 ), 00222 'fishbowl' => array( 00223 '*' => array( 00224 'createaccount' => false, 00225 'edit' => false, 00226 ), 00227 ), 00228 'private' => array( 00229 '*' => array( 00230 'createaccount' => false, 00231 'edit' => false, 00232 'read' => false, 00233 ), 00234 ), 00235 ); 00236 00242 public $licenses = array( 00243 'cc-by' => array( 00244 'url' => 'http://creativecommons.org/licenses/by/3.0/', 00245 'icon' => '{$wgStylePath}/common/images/cc-by.png', 00246 ), 00247 'cc-by-sa' => array( 00248 'url' => 'http://creativecommons.org/licenses/by-sa/3.0/', 00249 'icon' => '{$wgStylePath}/common/images/cc-by-sa.png', 00250 ), 00251 'cc-by-nc-sa' => array( 00252 'url' => 'http://creativecommons.org/licenses/by-nc-sa/3.0/', 00253 'icon' => '{$wgStylePath}/common/images/cc-by-nc-sa.png', 00254 ), 00255 'cc-0' => array( 00256 'url' => 'https://creativecommons.org/publicdomain/zero/1.0/', 00257 'icon' => '{$wgStylePath}/common/images/cc-0.png', 00258 ), 00259 'pd' => array( 00260 'url' => '', 00261 'icon' => '{$wgStylePath}/common/images/public-domain.png', 00262 ), 00263 'gfdl' => array( 00264 'url' => 'http://www.gnu.org/copyleft/fdl.html', 00265 'icon' => '{$wgStylePath}/common/images/gnu-fdl.png', 00266 ), 00267 'none' => array( 00268 'url' => '', 00269 'icon' => '', 00270 'text' => '' 00271 ), 00272 'cc-choose' => array( 00273 // Details will be filled in by the selector. 00274 'url' => '', 00275 'icon' => '', 00276 'text' => '', 00277 ), 00278 ); 00279 00283 protected $mediaWikiAnnounceUrl = 'https://lists.wikimedia.org/mailman/subscribe/mediawiki-announce'; 00284 00288 protected $mediaWikiAnnounceLanguages = array( 00289 'ca', 'cs', 'da', 'de', 'en', 'es', 'et', 'eu', 'fi', 'fr', 'hr', 'hu', 00290 'it', 'ja', 'ko', 'lt', 'nl', 'no', 'pl', 'pt', 'pt-br', 'ro', 'ru', 00291 'sl', 'sr', 'sv', 'tr', 'uk' 00292 ); 00293 00301 public abstract function showMessage( $msg /*, ... */ ); 00302 00307 public abstract function showError( $msg /*, ... */ ); 00308 00313 public abstract function showStatusMessage( Status $status ); 00314 00318 public function __construct() { 00319 global $wgExtensionMessagesFiles, $wgUser; 00320 00321 // Disable the i18n cache and LoadBalancer 00322 Language::getLocalisationCache()->disableBackend(); 00323 LBFactory::disableBackend(); 00324 00325 // Load the installer's i18n file. 00326 $wgExtensionMessagesFiles['MediawikiInstaller'] = 00327 dirname( __FILE__ ) . '/Installer.i18n.php'; 00328 00329 // Having a user with id = 0 safeguards us from DB access via User::loadOptions(). 00330 $wgUser = User::newFromId( 0 ); 00331 00332 $this->settings = $this->internalDefaults; 00333 00334 foreach ( $this->defaultVarNames as $var ) { 00335 $this->settings[$var] = $GLOBALS[$var]; 00336 } 00337 00338 $compiledDBs = array(); 00339 foreach ( self::getDBTypes() as $type ) { 00340 $installer = $this->getDBInstaller( $type ); 00341 00342 if ( !$installer->isCompiled() ) { 00343 continue; 00344 } 00345 $compiledDBs[] = $type; 00346 00347 $defaults = $installer->getGlobalDefaults(); 00348 00349 foreach ( $installer->getGlobalNames() as $var ) { 00350 if ( isset( $defaults[$var] ) ) { 00351 $this->settings[$var] = $defaults[$var]; 00352 } else { 00353 $this->settings[$var] = $GLOBALS[$var]; 00354 } 00355 } 00356 } 00357 $this->setVar( '_CompiledDBs', $compiledDBs ); 00358 00359 $this->parserTitle = Title::newFromText( 'Installer' ); 00360 $this->parserOptions = new ParserOptions; // language will be wrong :( 00361 $this->parserOptions->setEditSection( false ); 00362 } 00363 00369 public static function getDBTypes() { 00370 return self::$dbTypes; 00371 } 00372 00386 public function doEnvironmentChecks() { 00387 $phpVersion = phpversion(); 00388 if( version_compare( $phpVersion, self::MINIMUM_PHP_VERSION, '>=' ) ) { 00389 $this->showMessage( 'config-env-php', $phpVersion ); 00390 $good = true; 00391 } else { 00392 $this->showMessage( 'config-env-php-toolow', $phpVersion, self::MINIMUM_PHP_VERSION ); 00393 $good = false; 00394 } 00395 00396 if( $good ) { 00397 foreach ( $this->envChecks as $check ) { 00398 $status = $this->$check(); 00399 if ( $status === false ) { 00400 $good = false; 00401 } 00402 } 00403 } 00404 00405 $this->setVar( '_Environment', $good ); 00406 00407 return $good ? Status::newGood() : Status::newFatal( 'config-env-bad' ); 00408 } 00409 00416 public function setVar( $name, $value ) { 00417 $this->settings[$name] = $value; 00418 } 00419 00430 public function getVar( $name, $default = null ) { 00431 if ( !isset( $this->settings[$name] ) ) { 00432 return $default; 00433 } else { 00434 return $this->settings[$name]; 00435 } 00436 } 00437 00445 public function getDBInstaller( $type = false ) { 00446 if ( !$type ) { 00447 $type = $this->getVar( 'wgDBtype' ); 00448 } 00449 00450 $type = strtolower( $type ); 00451 00452 if ( !isset( $this->dbInstallers[$type] ) ) { 00453 $class = ucfirst( $type ). 'Installer'; 00454 $this->dbInstallers[$type] = new $class( $this ); 00455 } 00456 00457 return $this->dbInstallers[$type]; 00458 } 00459 00466 public static function getExistingLocalSettings() { 00467 global $IP; 00468 00469 wfSuppressWarnings(); 00470 $_lsExists = file_exists( "$IP/LocalSettings.php" ); 00471 wfRestoreWarnings(); 00472 00473 if( !$_lsExists ) { 00474 return false; 00475 } 00476 unset($_lsExists); 00477 00478 require( "$IP/includes/DefaultSettings.php" ); 00479 require( "$IP/LocalSettings.php" ); 00480 if ( file_exists( "$IP/AdminSettings.php" ) ) { 00481 require( "$IP/AdminSettings.php" ); 00482 } 00483 return get_defined_vars(); 00484 } 00485 00495 public function getFakePassword( $realPassword ) { 00496 return str_repeat( '*', strlen( $realPassword ) ); 00497 } 00498 00506 public function setPassword( $name, $value ) { 00507 if ( !preg_match( '/^\*+$/', $value ) ) { 00508 $this->setVar( $name, $value ); 00509 } 00510 } 00511 00523 public static function maybeGetWebserverPrimaryGroup() { 00524 if ( !function_exists( 'posix_getegid' ) || !function_exists( 'posix_getpwuid' ) ) { 00525 # I don't know this, this isn't UNIX. 00526 return null; 00527 } 00528 00529 # posix_getegid() *not* getmygid() because we want the group of the webserver, 00530 # not whoever owns the current script. 00531 $gid = posix_getegid(); 00532 $getpwuid = posix_getpwuid( $gid ); 00533 $group = $getpwuid['name']; 00534 00535 return $group; 00536 } 00537 00554 public function parse( $text, $lineStart = false ) { 00555 global $wgParser; 00556 00557 try { 00558 $out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart ); 00559 $html = $out->getText(); 00560 } catch ( DBAccessError $e ) { 00561 $html = '<!--DB access attempted during parse--> ' . htmlspecialchars( $text ); 00562 00563 if ( !empty( $this->debug ) ) { 00564 $html .= "<!--\n" . $e->getTraceAsString() . "\n-->"; 00565 } 00566 } 00567 00568 return $html; 00569 } 00570 00574 public function getParserOptions() { 00575 return $this->parserOptions; 00576 } 00577 00578 public function disableLinkPopups() { 00579 $this->parserOptions->setExternalLinkTarget( false ); 00580 } 00581 00582 public function restoreLinkPopups() { 00583 global $wgExternalLinkTarget; 00584 $this->parserOptions->setExternalLinkTarget( $wgExternalLinkTarget ); 00585 } 00586 00595 public function populateSiteStats( DatabaseInstaller $installer ) { 00596 $status = $installer->getConnection(); 00597 if ( !$status->isOK() ) { 00598 return $status; 00599 } 00600 $status->value->insert( 'site_stats', array( 00601 'ss_row_id' => 1, 00602 'ss_total_views' => 0, 00603 'ss_total_edits' => 0, 00604 'ss_good_articles' => 0, 00605 'ss_total_pages' => 0, 00606 'ss_users' => 0, 00607 'ss_images' => 0 ), 00608 __METHOD__, 'IGNORE' ); 00609 return Status::newGood(); 00610 } 00611 00615 public function exportVars() { 00616 foreach ( $this->settings as $name => $value ) { 00617 if ( substr( $name, 0, 2 ) == 'wg' ) { 00618 $GLOBALS[$name] = $value; 00619 } 00620 } 00621 } 00622 00627 protected function envCheckDB() { 00628 global $wgLang; 00629 00630 $allNames = array(); 00631 00632 foreach ( self::getDBTypes() as $name ) { 00633 $allNames[] = wfMsg( "config-type-$name" ); 00634 } 00635 00636 // cache initially available databases to make sure that everything will be displayed correctly 00637 // after a refresh on env checks page 00638 $databases = $this->getVar( '_CompiledDBs-preFilter' ); 00639 if ( !$databases ) { 00640 $databases = $this->getVar( '_CompiledDBs' ); 00641 $this->setVar( '_CompiledDBs-preFilter', $databases ); 00642 } 00643 00644 $databases = array_flip ( $databases ); 00645 foreach ( array_keys( $databases ) as $db ) { 00646 $installer = $this->getDBInstaller( $db ); 00647 $status = $installer->checkPrerequisites(); 00648 if ( !$status->isGood() ) { 00649 $this->showStatusMessage( $status ); 00650 } 00651 if ( !$status->isOK() ) { 00652 unset( $databases[$db] ); 00653 } 00654 } 00655 $databases = array_flip( $databases ); 00656 if ( !$databases ) { 00657 $this->showError( 'config-no-db', $wgLang->commaList( $allNames ) ); 00658 // @todo FIXME: This only works for the web installer! 00659 return false; 00660 } 00661 $this->setVar( '_CompiledDBs', $databases ); 00662 } 00663 00667 protected function envCheckRegisterGlobals() { 00668 if( wfIniGetBool( 'register_globals' ) ) { 00669 $this->showMessage( 'config-register-globals' ); 00670 } 00671 } 00672 00676 protected function envCheckBrokenXML() { 00677 $test = new PhpXmlBugTester(); 00678 if ( !$test->ok ) { 00679 $this->showError( 'config-brokenlibxml' ); 00680 return false; 00681 } 00682 } 00683 00688 protected function envCheckPHP531() { 00689 $test = new PhpRefCallBugTester; 00690 $test->execute(); 00691 if ( !$test->ok ) { 00692 $this->showError( 'config-using531', phpversion() ); 00693 return false; 00694 } 00695 } 00696 00700 protected function envCheckMagicQuotes() { 00701 if( wfIniGetBool( "magic_quotes_runtime" ) ) { 00702 $this->showError( 'config-magic-quotes-runtime' ); 00703 return false; 00704 } 00705 } 00706 00710 protected function envCheckMagicSybase() { 00711 if ( wfIniGetBool( 'magic_quotes_sybase' ) ) { 00712 $this->showError( 'config-magic-quotes-sybase' ); 00713 return false; 00714 } 00715 } 00716 00720 protected function envCheckMbstring() { 00721 if ( wfIniGetBool( 'mbstring.func_overload' ) ) { 00722 $this->showError( 'config-mbstring' ); 00723 return false; 00724 } 00725 } 00726 00730 protected function envCheckZE1() { 00731 if ( wfIniGetBool( 'zend.ze1_compatibility_mode' ) ) { 00732 $this->showError( 'config-ze1' ); 00733 return false; 00734 } 00735 } 00736 00740 protected function envCheckSafeMode() { 00741 if ( wfIniGetBool( 'safe_mode' ) ) { 00742 $this->setVar( '_SafeMode', true ); 00743 $this->showMessage( 'config-safe-mode' ); 00744 } 00745 } 00746 00750 protected function envCheckXML() { 00751 if ( !function_exists( "utf8_encode" ) ) { 00752 $this->showError( 'config-xml-bad' ); 00753 return false; 00754 } 00755 } 00756 00765 protected function envCheckPCRE() { 00766 if ( !function_exists( 'preg_match' ) ) { 00767 $this->showError( 'config-pcre' ); 00768 return false; 00769 } 00770 wfSuppressWarnings(); 00771 $regexd = preg_replace( '/[\x{0430}-\x{04FF}]/iu', '', '-АБВГД-' ); 00772 // Need to check for \p support too, as PCRE can be compiled 00773 // with utf8 support, but not unicode property support. 00774 // check that \p{Zs} (space separators) matches 00775 // U+3000 (Ideographic space) 00776 $regexprop = preg_replace( '/\p{Zs}/u', '', "-\xE3\x80\x80-" ); 00777 wfRestoreWarnings(); 00778 if ( $regexd != '--' || $regexprop != '--' ) { 00779 $this->showError( 'config-pcre-no-utf8' ); 00780 return false; 00781 } 00782 } 00783 00787 protected function envCheckMemory() { 00788 $limit = ini_get( 'memory_limit' ); 00789 00790 if ( !$limit || $limit == -1 ) { 00791 return true; 00792 } 00793 00794 $n = wfShorthandToInteger( $limit ); 00795 00796 if( $n < $this->minMemorySize * 1024 * 1024 ) { 00797 $newLimit = "{$this->minMemorySize}M"; 00798 00799 if( ini_set( "memory_limit", $newLimit ) === false ) { 00800 $this->showMessage( 'config-memory-bad', $limit ); 00801 } else { 00802 $this->showMessage( 'config-memory-raised', $limit, $newLimit ); 00803 $this->setVar( '_RaiseMemory', true ); 00804 } 00805 } else { 00806 return true; 00807 } 00808 } 00809 00813 protected function envCheckCache() { 00814 $caches = array(); 00815 foreach ( $this->objectCaches as $name => $function ) { 00816 if ( function_exists( $function ) ) { 00817 if ( $name == 'xcache' && !wfIniGetBool( 'xcache.var_size' ) ) { 00818 continue; 00819 } 00820 $caches[$name] = true; 00821 } 00822 } 00823 00824 if ( !$caches ) { 00825 $this->showMessage( 'config-no-cache' ); 00826 } 00827 00828 $this->setVar( '_Caches', $caches ); 00829 } 00830 00834 protected function envCheckModSecurity() { 00835 if ( self::apacheModulePresent( 'mod_security' ) ) { 00836 $this->showMessage( 'config-mod-security' ); 00837 } 00838 } 00839 00843 protected function envCheckDiff3() { 00844 $names = array( "gdiff3", "diff3", "diff3.exe" ); 00845 $versionInfo = array( '$1 --version 2>&1', 'GNU diffutils' ); 00846 00847 $diff3 = self::locateExecutableInDefaultPaths( $names, $versionInfo ); 00848 00849 if ( $diff3 ) { 00850 $this->setVar( 'wgDiff3', $diff3 ); 00851 } else { 00852 $this->setVar( 'wgDiff3', false ); 00853 $this->showMessage( 'config-diff3-bad' ); 00854 } 00855 } 00856 00860 protected function envCheckGraphics() { 00861 $names = array( wfIsWindows() ? 'convert.exe' : 'convert' ); 00862 $convert = self::locateExecutableInDefaultPaths( $names, array( '$1 -version', 'ImageMagick' ) ); 00863 00864 $this->setVar( 'wgImageMagickConvertCommand', '' ); 00865 if ( $convert ) { 00866 $this->setVar( 'wgImageMagickConvertCommand', $convert ); 00867 $this->showMessage( 'config-imagemagick', $convert ); 00868 return true; 00869 } elseif ( function_exists( 'imagejpeg' ) ) { 00870 $this->showMessage( 'config-gd' ); 00871 return true; 00872 } else { 00873 $this->showMessage( 'config-no-scaling' ); 00874 } 00875 } 00876 00880 protected function envCheckServer() { 00881 $server = $this->envGetDefaultServer(); 00882 $this->showMessage( 'config-using-server', $server ); 00883 $this->setVar( 'wgServer', $server ); 00884 } 00885 00890 protected abstract function envGetDefaultServer(); 00891 00896 protected function envCheckPath() { 00897 global $IP; 00898 $IP = dirname( dirname( dirname( __FILE__ ) ) ); 00899 $this->setVar( 'IP', $IP ); 00900 00901 $this->showMessage( 'config-using-uri', $this->getVar( 'wgServer' ), $this->getVar( 'wgScriptPath' ) ); 00902 return true; 00903 } 00904 00908 protected function envCheckExtension() { 00909 // @todo FIXME: Detect this properly 00910 if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) { 00911 $ext = 'php5'; 00912 } else { 00913 $ext = 'php'; 00914 } 00915 $this->setVar( 'wgScriptExtension', ".$ext" ); 00916 } 00917 00922 protected function envCheckShellLocale() { 00923 $os = php_uname( 's' ); 00924 $supported = array( 'Linux', 'SunOS', 'HP-UX', 'Darwin' ); # Tested these 00925 00926 if ( !in_array( $os, $supported ) ) { 00927 return true; 00928 } 00929 00930 # Get a list of available locales. 00931 $ret = false; 00932 $lines = wfShellExec( '/usr/bin/locale -a', $ret ); 00933 00934 if ( $ret ) { 00935 return true; 00936 } 00937 00938 $lines = wfArrayMap( 'trim', explode( "\n", $lines ) ); 00939 $candidatesByLocale = array(); 00940 $candidatesByLang = array(); 00941 00942 foreach ( $lines as $line ) { 00943 if ( $line === '' ) { 00944 continue; 00945 } 00946 00947 if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) { 00948 continue; 00949 } 00950 00951 list( $all, $lang, $territory, $charset, $modifier ) = $m; 00952 00953 $candidatesByLocale[$m[0]] = $m; 00954 $candidatesByLang[$lang][] = $m; 00955 } 00956 00957 # Try the current value of LANG. 00958 if ( isset( $candidatesByLocale[ getenv( 'LANG' ) ] ) ) { 00959 $this->setVar( 'wgShellLocale', getenv( 'LANG' ) ); 00960 return true; 00961 } 00962 00963 # Try the most common ones. 00964 $commonLocales = array( 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' ); 00965 foreach ( $commonLocales as $commonLocale ) { 00966 if ( isset( $candidatesByLocale[$commonLocale] ) ) { 00967 $this->setVar( 'wgShellLocale', $commonLocale ); 00968 return true; 00969 } 00970 } 00971 00972 # Is there an available locale in the Wiki's language? 00973 $wikiLang = $this->getVar( 'wgLanguageCode' ); 00974 00975 if ( isset( $candidatesByLang[$wikiLang] ) ) { 00976 $m = reset( $candidatesByLang[$wikiLang] ); 00977 $this->setVar( 'wgShellLocale', $m[0] ); 00978 return true; 00979 } 00980 00981 # Are there any at all? 00982 if ( count( $candidatesByLocale ) ) { 00983 $m = reset( $candidatesByLocale ); 00984 $this->setVar( 'wgShellLocale', $m[0] ); 00985 return true; 00986 } 00987 00988 # Give up. 00989 return true; 00990 } 00991 00995 protected function envCheckUploadsDirectory() { 00996 global $IP; 00997 00998 $dir = $IP . '/images/'; 00999 $url = $this->getVar( 'wgServer' ) . $this->getVar( 'wgScriptPath' ) . '/images/'; 01000 $safe = !$this->dirIsExecutable( $dir, $url ); 01001 01002 if ( $safe ) { 01003 return true; 01004 } else { 01005 $this->showMessage( 'config-uploads-not-safe', $dir ); 01006 } 01007 } 01008 01014 protected function envCheckSuhosinMaxValueLength() { 01015 $maxValueLength = ini_get( 'suhosin.get.max_value_length' ); 01016 if ( $maxValueLength > 0 ) { 01017 if( $maxValueLength < 1024 ) { 01018 # Only warn if the value is below the sane 1024 01019 $this->showMessage( 'config-suhosin-max-value-length', $maxValueLength ); 01020 } 01021 } else { 01022 $maxValueLength = -1; 01023 } 01024 $this->setVar( 'wgResourceLoaderMaxQueryLength', $maxValueLength ); 01025 } 01026 01032 protected function unicodeChar( $c ) { 01033 $c = hexdec($c); 01034 if ($c <= 0x7F) { 01035 return chr($c); 01036 } elseif ($c <= 0x7FF) { 01037 return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F); 01038 } elseif ($c <= 0xFFFF) { 01039 return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F) 01040 . chr(0x80 | $c & 0x3F); 01041 } elseif ($c <= 0x10FFFF) { 01042 return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F) 01043 . chr(0x80 | $c >> 6 & 0x3F) 01044 . chr(0x80 | $c & 0x3F); 01045 } else { 01046 return false; 01047 } 01048 } 01049 01050 01054 protected function envCheckLibicu() { 01055 $utf8 = function_exists( 'utf8_normalize' ); 01056 $intl = function_exists( 'normalizer_normalize' ); 01057 01065 $not_normal_c = $this->unicodeChar("FA6C"); 01066 $normal_c = $this->unicodeChar("242EE"); 01067 01068 $useNormalizer = 'php'; 01069 $needsUpdate = false; 01070 01075 if( $utf8 ) { 01076 $useNormalizer = 'utf8'; 01077 $utf8 = utf8_normalize( $not_normal_c, UtfNormal::UNORM_NFC ); 01078 if ( $utf8 !== $normal_c ) $needsUpdate = true; 01079 } 01080 if( $intl ) { 01081 $useNormalizer = 'intl'; 01082 $intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C ); 01083 if ( $intl !== $normal_c ) $needsUpdate = true; 01084 } 01085 01086 // Uses messages 'config-unicode-using-php', 'config-unicode-using-utf8', 'config-unicode-using-intl' 01087 if( $useNormalizer === 'php' ) { 01088 $this->showMessage( 'config-unicode-pure-php-warning' ); 01089 } else { 01090 $this->showMessage( 'config-unicode-using-' . $useNormalizer ); 01091 if( $needsUpdate ) { 01092 $this->showMessage( 'config-unicode-update-warning' ); 01093 } 01094 } 01095 } 01096 01097 protected function envCheckCtype() { 01098 if ( !function_exists( 'ctype_digit' ) ) { 01099 $this->showError( 'config-ctype' ); 01100 return false; 01101 } 01102 } 01103 01111 protected static function getPossibleBinPaths() { 01112 return array_merge( 01113 array( '/usr/bin', '/usr/local/bin', '/opt/csw/bin', 01114 '/usr/gnu/bin', '/usr/sfw/bin', '/sw/bin', '/opt/local/bin' ), 01115 explode( PATH_SEPARATOR, getenv( 'PATH' ) ) 01116 ); 01117 } 01118 01135 public static function locateExecutable( $path, $names, $versionInfo = false ) { 01136 if ( !is_array( $names ) ) { 01137 $names = array( $names ); 01138 } 01139 01140 foreach ( $names as $name ) { 01141 $command = $path . DIRECTORY_SEPARATOR . $name; 01142 01143 wfSuppressWarnings(); 01144 $file_exists = file_exists( $command ); 01145 wfRestoreWarnings(); 01146 01147 if ( $file_exists ) { 01148 if ( !$versionInfo ) { 01149 return $command; 01150 } 01151 01152 $file = str_replace( '$1', wfEscapeShellArg( $command ), $versionInfo[0] ); 01153 if ( strstr( wfShellExec( $file ), $versionInfo[1] ) !== false ) { 01154 return $command; 01155 } 01156 } 01157 } 01158 return false; 01159 } 01160 01168 public static function locateExecutableInDefaultPaths( $names, $versionInfo = false ) { 01169 foreach( self::getPossibleBinPaths() as $path ) { 01170 $exe = self::locateExecutable( $path, $names, $versionInfo ); 01171 if( $exe !== false ) { 01172 return $exe; 01173 } 01174 } 01175 return false; 01176 } 01177 01183 public function dirIsExecutable( $dir, $url ) { 01184 $scriptTypes = array( 01185 'php' => array( 01186 "<?php echo 'ex' . 'ec';", 01187 "#!/var/env php5\n<?php echo 'ex' . 'ec';", 01188 ), 01189 ); 01190 01191 // it would be good to check other popular languages here, but it'll be slow. 01192 01193 wfSuppressWarnings(); 01194 01195 foreach ( $scriptTypes as $ext => $contents ) { 01196 foreach ( $contents as $source ) { 01197 $file = 'exectest.' . $ext; 01198 01199 if ( !file_put_contents( $dir . $file, $source ) ) { 01200 break; 01201 } 01202 01203 try { 01204 $text = Http::get( $url . $file, array( 'timeout' => 3 ) ); 01205 } 01206 catch( MWException $e ) { 01207 // Http::get throws with allow_url_fopen = false and no curl extension. 01208 $text = null; 01209 } 01210 unlink( $dir . $file ); 01211 01212 if ( $text == 'exec' ) { 01213 wfRestoreWarnings(); 01214 return $ext; 01215 } 01216 } 01217 } 01218 01219 wfRestoreWarnings(); 01220 01221 return false; 01222 } 01223 01230 public static function apacheModulePresent( $moduleName ) { 01231 if ( function_exists( 'apache_get_modules' ) && in_array( $moduleName, apache_get_modules() ) ) { 01232 return true; 01233 } 01234 // try it the hard way 01235 ob_start(); 01236 phpinfo( INFO_MODULES ); 01237 $info = ob_get_clean(); 01238 return strpos( $info, $moduleName ) !== false; 01239 } 01240 01246 public function setParserLanguage( $lang ) { 01247 $this->parserOptions->setTargetLanguage( $lang ); 01248 $this->parserOptions->setUserLang( $lang ); 01249 } 01250 01256 protected function getDocUrl( $page ) { 01257 return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page ); 01258 } 01259 01266 public function findExtensions() { 01267 if( $this->getVar( 'IP' ) === null ) { 01268 return false; 01269 } 01270 01271 $exts = array(); 01272 $extDir = $this->getVar( 'IP' ) . '/extensions'; 01273 $dh = opendir( $extDir ); 01274 01275 while ( ( $file = readdir( $dh ) ) !== false ) { 01276 if( !is_dir( "$extDir/$file" ) ) { 01277 continue; 01278 } 01279 if( file_exists( "$extDir/$file/$file.php" ) ) { 01280 $exts[] = $file; 01281 } 01282 } 01283 natcasesort( $exts ); 01284 01285 return $exts; 01286 } 01287 01293 protected function includeExtensions() { 01294 global $IP; 01295 $exts = $this->getVar( '_Extensions' ); 01296 $IP = $this->getVar( 'IP' ); 01297 01306 global $wgAutoloadClasses; 01307 $wgAutoloadClasses = array(); 01308 01309 require( "$IP/includes/DefaultSettings.php" ); 01310 01311 foreach( $exts as $e ) { 01312 require_once( "$IP/extensions/$e/$e.php" ); 01313 } 01314 01315 $hooksWeWant = isset( $wgHooks['LoadExtensionSchemaUpdates'] ) ? 01316 $wgHooks['LoadExtensionSchemaUpdates'] : array(); 01317 01318 // Unset everyone else's hooks. Lord knows what someone might be doing 01319 // in ParserFirstCallInit (see bug 27171) 01320 $GLOBALS['wgHooks'] = array( 'LoadExtensionSchemaUpdates' => $hooksWeWant ); 01321 01322 return Status::newGood(); 01323 } 01324 01337 protected function getInstallSteps( DatabaseInstaller $installer ) { 01338 $coreInstallSteps = array( 01339 array( 'name' => 'database', 'callback' => array( $installer, 'setupDatabase' ) ), 01340 array( 'name' => 'tables', 'callback' => array( $installer, 'createTables' ) ), 01341 array( 'name' => 'interwiki', 'callback' => array( $installer, 'populateInterwikiTable' ) ), 01342 array( 'name' => 'stats', 'callback' => array( $this, 'populateSiteStats' ) ), 01343 array( 'name' => 'keys', 'callback' => array( $this, 'generateKeys' ) ), 01344 array( 'name' => 'sysop', 'callback' => array( $this, 'createSysop' ) ), 01345 array( 'name' => 'mainpage', 'callback' => array( $this, 'createMainpage' ) ), 01346 ); 01347 01348 // Build the array of install steps starting from the core install list, 01349 // then adding any callbacks that wanted to attach after a given step 01350 foreach( $coreInstallSteps as $step ) { 01351 $this->installSteps[] = $step; 01352 if( isset( $this->extraInstallSteps[ $step['name'] ] ) ) { 01353 $this->installSteps = array_merge( 01354 $this->installSteps, 01355 $this->extraInstallSteps[ $step['name'] ] 01356 ); 01357 } 01358 } 01359 01360 // Prepend any steps that want to be at the beginning 01361 if( isset( $this->extraInstallSteps['BEGINNING'] ) ) { 01362 $this->installSteps = array_merge( 01363 $this->extraInstallSteps['BEGINNING'], 01364 $this->installSteps 01365 ); 01366 } 01367 01368 // Extensions should always go first, chance to tie into hooks and such 01369 if( count( $this->getVar( '_Extensions' ) ) ) { 01370 array_unshift( $this->installSteps, 01371 array( 'name' => 'extensions', 'callback' => array( $this, 'includeExtensions' ) ) 01372 ); 01373 $this->installSteps[] = array( 01374 'name' => 'extension-tables', 01375 'callback' => array( $installer, 'createExtensionTables' ) 01376 ); 01377 } 01378 return $this->installSteps; 01379 } 01380 01389 public function performInstallation( $startCB, $endCB ) { 01390 $installResults = array(); 01391 $installer = $this->getDBInstaller(); 01392 $installer->preInstall(); 01393 $steps = $this->getInstallSteps( $installer ); 01394 foreach( $steps as $stepObj ) { 01395 $name = $stepObj['name']; 01396 call_user_func_array( $startCB, array( $name ) ); 01397 01398 // Perform the callback step 01399 $status = call_user_func( $stepObj['callback'], $installer ); 01400 01401 // Output and save the results 01402 call_user_func( $endCB, $name, $status ); 01403 $installResults[$name] = $status; 01404 01405 // If we've hit some sort of fatal, we need to bail. 01406 // Callback already had a chance to do output above. 01407 if( !$status->isOk() ) { 01408 break; 01409 } 01410 } 01411 if( $status->isOk() ) { 01412 $this->setVar( '_InstallDone', true ); 01413 } 01414 return $installResults; 01415 } 01416 01422 public function generateKeys() { 01423 $keys = array( 'wgSecretKey' => 64 ); 01424 if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) { 01425 $keys['wgUpgradeKey'] = 16; 01426 } 01427 return $this->doGenerateKeys( $keys ); 01428 } 01429 01437 protected function doGenerateKeys( $keys ) { 01438 $status = Status::newGood(); 01439 01440 $strong = true; 01441 foreach ( $keys as $name => $length ) { 01442 $secretKey = MWCryptRand::generateHex( $length, true ); 01443 if ( !MWCryptRand::wasStrong() ) { 01444 $strong = false; 01445 } 01446 01447 $this->setVar( $name, $secretKey ); 01448 } 01449 01450 if ( !$strong ) { 01451 $names = array_keys( $keys ); 01452 $names = preg_replace( '/^(.*)$/', '\$$1', $names ); 01453 global $wgLang; 01454 $status->warning( 'config-insecure-keys', $wgLang->listToText( $names ), count( $names ) ); 01455 } 01456 01457 return $status; 01458 } 01459 01465 protected function createSysop() { 01466 $name = $this->getVar( '_AdminName' ); 01467 $user = User::newFromName( $name ); 01468 01469 if ( !$user ) { 01470 // We should've validated this earlier anyway! 01471 return Status::newFatal( 'config-admin-error-user', $name ); 01472 } 01473 01474 if ( $user->idForName() == 0 ) { 01475 $user->addToDatabase(); 01476 01477 try { 01478 $user->setPassword( $this->getVar( '_AdminPassword' ) ); 01479 } catch( PasswordError $pwe ) { 01480 return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() ); 01481 } 01482 01483 $user->addGroup( 'sysop' ); 01484 $user->addGroup( 'bureaucrat' ); 01485 if( $this->getVar( '_AdminEmail' ) ) { 01486 $user->setEmail( $this->getVar( '_AdminEmail' ) ); 01487 } 01488 $user->saveSettings(); 01489 01490 // Update user count 01491 $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 ); 01492 $ssUpdate->doUpdate(); 01493 } 01494 $status = Status::newGood(); 01495 01496 if( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) { 01497 $this->subscribeToMediaWikiAnnounce( $status ); 01498 } 01499 01500 return $status; 01501 } 01502 01506 private function subscribeToMediaWikiAnnounce( Status $s ) { 01507 $params = array( 01508 'email' => $this->getVar( '_AdminEmail' ), 01509 'language' => 'en', 01510 'digest' => 0 01511 ); 01512 01513 // Mailman doesn't support as many languages as we do, so check to make 01514 // sure their selected language is available 01515 $myLang = $this->getVar( '_UserLang' ); 01516 if( in_array( $myLang, $this->mediaWikiAnnounceLanguages ) ) { 01517 $myLang = $myLang == 'pt-br' ? 'pt_BR' : $myLang; // rewrite to Mailman's pt_BR 01518 $params['language'] = $myLang; 01519 } 01520 01521 if( MWHttpRequest::canMakeRequests() ) { 01522 $res = MWHttpRequest::factory( $this->mediaWikiAnnounceUrl, 01523 array( 'method' => 'POST', 'postData' => $params ) )->execute(); 01524 if( !$res->isOK() ) { 01525 $s->warning( 'config-install-subscribe-fail', $res->getMessage() ); 01526 } 01527 } else { 01528 $s->warning( 'config-install-subscribe-notpossible' ); 01529 } 01530 } 01531 01538 protected function createMainpage( DatabaseInstaller $installer ) { 01539 $status = Status::newGood(); 01540 try { 01541 $page = WikiPage::factory( Title::newMainPage() ); 01542 $page->doEdit( wfMsgForContent( 'mainpagetext' ) . "\n\n" . 01543 wfMsgForContent( 'mainpagedocfooter' ), 01544 '', 01545 EDIT_NEW, 01546 false, 01547 User::newFromName( 'MediaWiki default' ) ); 01548 } catch (MWException $e) { 01549 //using raw, because $wgShowExceptionDetails can not be set yet 01550 $status->fatal( 'config-install-mainpage-failed', $e->getMessage() ); 01551 } 01552 01553 return $status; 01554 } 01555 01559 public static function overrideConfig() { 01560 define( 'MW_NO_SESSION', 1 ); 01561 01562 // Don't access the database 01563 $GLOBALS['wgUseDatabaseMessages'] = false; 01564 // Debug-friendly 01565 $GLOBALS['wgShowExceptionDetails'] = true; 01566 // Don't break forms 01567 $GLOBALS['wgExternalLinkTarget'] = '_blank'; 01568 01569 // Extended debugging 01570 $GLOBALS['wgShowSQLErrors'] = true; 01571 $GLOBALS['wgShowDBErrorBacktrace'] = true; 01572 01573 // Allow multiple ob_flush() calls 01574 $GLOBALS['wgDisableOutputCompression'] = true; 01575 01576 // Use a sensible cookie prefix (not my_wiki) 01577 $GLOBALS['wgCookiePrefix'] = 'mw_installer'; 01578 01579 // Some of the environment checks make shell requests, remove limits 01580 $GLOBALS['wgMaxShellMemory'] = 0; 01581 } 01582 01590 public function addInstallStep( $callback, $findStep = 'BEGINNING' ) { 01591 $this->extraInstallSteps[$findStep][] = $callback; 01592 } 01593 01598 protected function disableTimeLimit() { 01599 wfSuppressWarnings(); 01600 set_time_limit( 0 ); 01601 wfRestoreWarnings(); 01602 } 01603 }