MediaWiki
REL1_20
|
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 00047 protected $settings; 00048 00054 protected $dbInstallers = array(); 00055 00061 protected $minMemorySize = 50; 00062 00068 protected $parserTitle; 00069 00075 protected $parserOptions; 00076 00086 protected static $dbTypes = array( 00087 'mysql', 00088 'postgres', 00089 'oracle', 00090 'sqlite', 00091 'ibm_db2', 00092 ); 00093 00101 protected $envChecks = array( 00102 'envCheckDB', 00103 'envCheckRegisterGlobals', 00104 'envCheckBrokenXML', 00105 'envCheckPHP531', 00106 'envCheckMagicQuotes', 00107 'envCheckMagicSybase', 00108 'envCheckMbstring', 00109 'envCheckZE1', 00110 'envCheckSafeMode', 00111 'envCheckXML', 00112 'envCheckPCRE', 00113 'envCheckMemory', 00114 'envCheckCache', 00115 'envCheckModSecurity', 00116 'envCheckDiff3', 00117 'envCheckGraphics', 00118 'envCheckServer', 00119 'envCheckPath', 00120 'envCheckExtension', 00121 'envCheckShellLocale', 00122 'envCheckUploadsDirectory', 00123 'envCheckLibicu', 00124 'envCheckSuhosinMaxValueLength', 00125 'envCheckCtype', 00126 ); 00127 00135 protected $defaultVarNames = array( 00136 'wgSitename', 00137 'wgPasswordSender', 00138 'wgLanguageCode', 00139 'wgRightsIcon', 00140 'wgRightsText', 00141 'wgRightsUrl', 00142 'wgMainCacheType', 00143 'wgEnableEmail', 00144 'wgEnableUserEmail', 00145 'wgEnotifUserTalk', 00146 'wgEnotifWatchlist', 00147 'wgEmailAuthentication', 00148 'wgDBtype', 00149 'wgDiff3', 00150 'wgImageMagickConvertCommand', 00151 'IP', 00152 'wgServer', 00153 'wgScriptPath', 00154 'wgScriptExtension', 00155 'wgMetaNamespace', 00156 'wgDeletedDirectory', 00157 'wgEnableUploads', 00158 'wgLogo', 00159 'wgShellLocale', 00160 'wgSecretKey', 00161 'wgUseInstantCommons', 00162 'wgUpgradeKey', 00163 'wgDefaultSkin', 00164 'wgResourceLoaderMaxQueryLength', 00165 ); 00166 00174 protected $internalDefaults = array( 00175 '_UserLang' => 'en', 00176 '_Environment' => false, 00177 '_CompiledDBs' => array(), 00178 '_SafeMode' => false, 00179 '_RaiseMemory' => false, 00180 '_UpgradeDone' => false, 00181 '_InstallDone' => false, 00182 '_Caches' => array(), 00183 '_InstallPassword' => '', 00184 '_SameAccount' => true, 00185 '_CreateDBAccount' => false, 00186 '_NamespaceType' => 'site-name', 00187 '_AdminName' => '', // will be set later, when the user selects language 00188 '_AdminPassword' => '', 00189 '_AdminPassword2' => '', 00190 '_AdminEmail' => '', 00191 '_Subscribe' => false, 00192 '_SkipOptional' => 'continue', 00193 '_RightsProfile' => 'wiki', 00194 '_LicenseCode' => 'none', 00195 '_CCDone' => false, 00196 '_Extensions' => array(), 00197 '_MemCachedServers' => '', 00198 '_UpgradeKeySupplied' => false, 00199 '_ExistingDBSettings' => false, 00200 ); 00201 00207 private $installSteps = array(); 00208 00214 protected $extraInstallSteps = array(); 00215 00221 protected $objectCaches = array( 00222 'xcache' => 'xcache_get', 00223 'apc' => 'apc_fetch', 00224 'wincache' => 'wincache_ucache_get' 00225 ); 00226 00232 public $rightsProfiles = array( 00233 'wiki' => array(), 00234 'no-anon' => array( 00235 '*' => array( 'edit' => false ) 00236 ), 00237 'fishbowl' => array( 00238 '*' => array( 00239 'createaccount' => false, 00240 'edit' => false, 00241 ), 00242 ), 00243 'private' => array( 00244 '*' => array( 00245 'createaccount' => false, 00246 'edit' => false, 00247 'read' => false, 00248 ), 00249 ), 00250 ); 00251 00257 public $licenses = array( 00258 'cc-by' => array( 00259 'url' => 'http://creativecommons.org/licenses/by/3.0/', 00260 'icon' => '{$wgStylePath}/common/images/cc-by.png', 00261 ), 00262 'cc-by-sa' => array( 00263 'url' => 'http://creativecommons.org/licenses/by-sa/3.0/', 00264 'icon' => '{$wgStylePath}/common/images/cc-by-sa.png', 00265 ), 00266 'cc-by-nc-sa' => array( 00267 'url' => 'http://creativecommons.org/licenses/by-nc-sa/3.0/', 00268 'icon' => '{$wgStylePath}/common/images/cc-by-nc-sa.png', 00269 ), 00270 'cc-0' => array( 00271 'url' => 'https://creativecommons.org/publicdomain/zero/1.0/', 00272 'icon' => '{$wgStylePath}/common/images/cc-0.png', 00273 ), 00274 'pd' => array( 00275 'url' => '', 00276 'icon' => '{$wgStylePath}/common/images/public-domain.png', 00277 ), 00278 'gfdl' => array( 00279 'url' => 'http://www.gnu.org/copyleft/fdl.html', 00280 'icon' => '{$wgStylePath}/common/images/gnu-fdl.png', 00281 ), 00282 'none' => array( 00283 'url' => '', 00284 'icon' => '', 00285 'text' => '' 00286 ), 00287 'cc-choose' => array( 00288 // Details will be filled in by the selector. 00289 'url' => '', 00290 'icon' => '', 00291 'text' => '', 00292 ), 00293 ); 00294 00298 protected $mediaWikiAnnounceUrl = 'https://lists.wikimedia.org/mailman/subscribe/mediawiki-announce'; 00299 00303 protected $mediaWikiAnnounceLanguages = array( 00304 'ca', 'cs', 'da', 'de', 'en', 'es', 'et', 'eu', 'fi', 'fr', 'hr', 'hu', 00305 'it', 'ja', 'ko', 'lt', 'nl', 'no', 'pl', 'pt', 'pt-br', 'ro', 'ru', 00306 'sl', 'sr', 'sv', 'tr', 'uk' 00307 ); 00308 00316 public abstract function showMessage( $msg /*, ... */ ); 00317 00322 public abstract function showError( $msg /*, ... */ ); 00323 00328 public abstract function showStatusMessage( Status $status ); 00329 00333 public function __construct() { 00334 global $wgExtensionMessagesFiles, $wgUser; 00335 00336 // Disable the i18n cache and LoadBalancer 00337 Language::getLocalisationCache()->disableBackend(); 00338 LBFactory::disableBackend(); 00339 00340 // Load the installer's i18n file. 00341 $wgExtensionMessagesFiles['MediawikiInstaller'] = 00342 __DIR__ . '/Installer.i18n.php'; 00343 00344 // Having a user with id = 0 safeguards us from DB access via User::loadOptions(). 00345 $wgUser = User::newFromId( 0 ); 00346 00347 $this->settings = $this->internalDefaults; 00348 00349 foreach ( $this->defaultVarNames as $var ) { 00350 $this->settings[$var] = $GLOBALS[$var]; 00351 } 00352 00353 $compiledDBs = array(); 00354 foreach ( self::getDBTypes() as $type ) { 00355 $installer = $this->getDBInstaller( $type ); 00356 00357 if ( !$installer->isCompiled() ) { 00358 continue; 00359 } 00360 $compiledDBs[] = $type; 00361 00362 $defaults = $installer->getGlobalDefaults(); 00363 00364 foreach ( $installer->getGlobalNames() as $var ) { 00365 if ( isset( $defaults[$var] ) ) { 00366 $this->settings[$var] = $defaults[$var]; 00367 } else { 00368 $this->settings[$var] = $GLOBALS[$var]; 00369 } 00370 } 00371 } 00372 $this->setVar( '_CompiledDBs', $compiledDBs ); 00373 00374 $this->parserTitle = Title::newFromText( 'Installer' ); 00375 $this->parserOptions = new ParserOptions; // language will be wrong :( 00376 $this->parserOptions->setEditSection( false ); 00377 } 00378 00384 public static function getDBTypes() { 00385 return self::$dbTypes; 00386 } 00387 00401 public function doEnvironmentChecks() { 00402 $phpVersion = phpversion(); 00403 if( version_compare( $phpVersion, self::MINIMUM_PHP_VERSION, '>=' ) ) { 00404 $this->showMessage( 'config-env-php', $phpVersion ); 00405 $good = true; 00406 } else { 00407 $this->showMessage( 'config-env-php-toolow', $phpVersion, self::MINIMUM_PHP_VERSION ); 00408 $good = false; 00409 } 00410 00411 if( $good ) { 00412 foreach ( $this->envChecks as $check ) { 00413 $status = $this->$check(); 00414 if ( $status === false ) { 00415 $good = false; 00416 } 00417 } 00418 } 00419 00420 $this->setVar( '_Environment', $good ); 00421 00422 return $good ? Status::newGood() : Status::newFatal( 'config-env-bad' ); 00423 } 00424 00431 public function setVar( $name, $value ) { 00432 $this->settings[$name] = $value; 00433 } 00434 00445 public function getVar( $name, $default = null ) { 00446 if ( !isset( $this->settings[$name] ) ) { 00447 return $default; 00448 } else { 00449 return $this->settings[$name]; 00450 } 00451 } 00452 00460 public function getDBInstaller( $type = false ) { 00461 if ( !$type ) { 00462 $type = $this->getVar( 'wgDBtype' ); 00463 } 00464 00465 $type = strtolower( $type ); 00466 00467 if ( !isset( $this->dbInstallers[$type] ) ) { 00468 $class = ucfirst( $type ). 'Installer'; 00469 $this->dbInstallers[$type] = new $class( $this ); 00470 } 00471 00472 return $this->dbInstallers[$type]; 00473 } 00474 00481 public static function getExistingLocalSettings() { 00482 global $IP; 00483 00484 wfSuppressWarnings(); 00485 $_lsExists = file_exists( "$IP/LocalSettings.php" ); 00486 wfRestoreWarnings(); 00487 00488 if( !$_lsExists ) { 00489 return false; 00490 } 00491 unset($_lsExists); 00492 00493 require( "$IP/includes/DefaultSettings.php" ); 00494 require( "$IP/LocalSettings.php" ); 00495 if ( file_exists( "$IP/AdminSettings.php" ) ) { 00496 require( "$IP/AdminSettings.php" ); 00497 } 00498 return get_defined_vars(); 00499 } 00500 00510 public function getFakePassword( $realPassword ) { 00511 return str_repeat( '*', strlen( $realPassword ) ); 00512 } 00513 00521 public function setPassword( $name, $value ) { 00522 if ( !preg_match( '/^\*+$/', $value ) ) { 00523 $this->setVar( $name, $value ); 00524 } 00525 } 00526 00538 public static function maybeGetWebserverPrimaryGroup() { 00539 if ( !function_exists( 'posix_getegid' ) || !function_exists( 'posix_getpwuid' ) ) { 00540 # I don't know this, this isn't UNIX. 00541 return null; 00542 } 00543 00544 # posix_getegid() *not* getmygid() because we want the group of the webserver, 00545 # not whoever owns the current script. 00546 $gid = posix_getegid(); 00547 $getpwuid = posix_getpwuid( $gid ); 00548 $group = $getpwuid['name']; 00549 00550 return $group; 00551 } 00552 00569 public function parse( $text, $lineStart = false ) { 00570 global $wgParser; 00571 00572 try { 00573 $out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart ); 00574 $html = $out->getText(); 00575 } catch ( DBAccessError $e ) { 00576 $html = '<!--DB access attempted during parse--> ' . htmlspecialchars( $text ); 00577 00578 if ( !empty( $this->debug ) ) { 00579 $html .= "<!--\n" . $e->getTraceAsString() . "\n-->"; 00580 } 00581 } 00582 00583 return $html; 00584 } 00585 00589 public function getParserOptions() { 00590 return $this->parserOptions; 00591 } 00592 00593 public function disableLinkPopups() { 00594 $this->parserOptions->setExternalLinkTarget( false ); 00595 } 00596 00597 public function restoreLinkPopups() { 00598 global $wgExternalLinkTarget; 00599 $this->parserOptions->setExternalLinkTarget( $wgExternalLinkTarget ); 00600 } 00601 00610 public function populateSiteStats( DatabaseInstaller $installer ) { 00611 $status = $installer->getConnection(); 00612 if ( !$status->isOK() ) { 00613 return $status; 00614 } 00615 $status->value->insert( 'site_stats', array( 00616 'ss_row_id' => 1, 00617 'ss_total_views' => 0, 00618 'ss_total_edits' => 0, 00619 'ss_good_articles' => 0, 00620 'ss_total_pages' => 0, 00621 'ss_users' => 0, 00622 'ss_images' => 0 ), 00623 __METHOD__, 'IGNORE' ); 00624 return Status::newGood(); 00625 } 00626 00630 public function exportVars() { 00631 foreach ( $this->settings as $name => $value ) { 00632 if ( substr( $name, 0, 2 ) == 'wg' ) { 00633 $GLOBALS[$name] = $value; 00634 } 00635 } 00636 } 00637 00642 protected function envCheckDB() { 00643 global $wgLang; 00644 00645 $allNames = array(); 00646 00647 foreach ( self::getDBTypes() as $name ) { 00648 $allNames[] = wfMessage( "config-type-$name" )->text(); 00649 } 00650 00651 // cache initially available databases to make sure that everything will be displayed correctly 00652 // after a refresh on env checks page 00653 $databases = $this->getVar( '_CompiledDBs-preFilter' ); 00654 if ( !$databases ) { 00655 $databases = $this->getVar( '_CompiledDBs' ); 00656 $this->setVar( '_CompiledDBs-preFilter', $databases ); 00657 } 00658 00659 $databases = array_flip ( $databases ); 00660 foreach ( array_keys( $databases ) as $db ) { 00661 $installer = $this->getDBInstaller( $db ); 00662 $status = $installer->checkPrerequisites(); 00663 if ( !$status->isGood() ) { 00664 $this->showStatusMessage( $status ); 00665 } 00666 if ( !$status->isOK() ) { 00667 unset( $databases[$db] ); 00668 } 00669 } 00670 $databases = array_flip( $databases ); 00671 if ( !$databases ) { 00672 $this->showError( 'config-no-db', $wgLang->commaList( $allNames ) ); 00673 // @todo FIXME: This only works for the web installer! 00674 return false; 00675 } 00676 $this->setVar( '_CompiledDBs', $databases ); 00677 return true; 00678 } 00679 00683 protected function envCheckRegisterGlobals() { 00684 if( wfIniGetBool( 'register_globals' ) ) { 00685 $this->showMessage( 'config-register-globals' ); 00686 } 00687 } 00688 00693 protected function envCheckBrokenXML() { 00694 $test = new PhpXmlBugTester(); 00695 if ( !$test->ok ) { 00696 $this->showError( 'config-brokenlibxml' ); 00697 return false; 00698 } 00699 return true; 00700 } 00701 00707 protected function envCheckPHP531() { 00708 $test = new PhpRefCallBugTester; 00709 $test->execute(); 00710 if ( !$test->ok ) { 00711 $this->showError( 'config-using531', phpversion() ); 00712 return false; 00713 } 00714 return true; 00715 } 00716 00721 protected function envCheckMagicQuotes() { 00722 if( wfIniGetBool( "magic_quotes_runtime" ) ) { 00723 $this->showError( 'config-magic-quotes-runtime' ); 00724 return false; 00725 } 00726 return true; 00727 } 00728 00733 protected function envCheckMagicSybase() { 00734 if ( wfIniGetBool( 'magic_quotes_sybase' ) ) { 00735 $this->showError( 'config-magic-quotes-sybase' ); 00736 return false; 00737 } 00738 return true; 00739 } 00740 00745 protected function envCheckMbstring() { 00746 if ( wfIniGetBool( 'mbstring.func_overload' ) ) { 00747 $this->showError( 'config-mbstring' ); 00748 return false; 00749 } 00750 return true; 00751 } 00752 00757 protected function envCheckZE1() { 00758 if ( wfIniGetBool( 'zend.ze1_compatibility_mode' ) ) { 00759 $this->showError( 'config-ze1' ); 00760 return false; 00761 } 00762 return true; 00763 } 00764 00769 protected function envCheckSafeMode() { 00770 if ( wfIniGetBool( 'safe_mode' ) ) { 00771 $this->setVar( '_SafeMode', true ); 00772 $this->showMessage( 'config-safe-mode' ); 00773 } 00774 return true; 00775 } 00776 00781 protected function envCheckXML() { 00782 if ( !function_exists( "utf8_encode" ) ) { 00783 $this->showError( 'config-xml-bad' ); 00784 return false; 00785 } 00786 return true; 00787 } 00788 00797 protected function envCheckPCRE() { 00798 if ( !function_exists( 'preg_match' ) ) { 00799 $this->showError( 'config-pcre' ); 00800 return false; 00801 } 00802 wfSuppressWarnings(); 00803 $regexd = preg_replace( '/[\x{0430}-\x{04FF}]/iu', '', '-АБВГД-' ); 00804 // Need to check for \p support too, as PCRE can be compiled 00805 // with utf8 support, but not unicode property support. 00806 // check that \p{Zs} (space separators) matches 00807 // U+3000 (Ideographic space) 00808 $regexprop = preg_replace( '/\p{Zs}/u', '', "-\xE3\x80\x80-" ); 00809 wfRestoreWarnings(); 00810 if ( $regexd != '--' || $regexprop != '--' ) { 00811 $this->showError( 'config-pcre-no-utf8' ); 00812 return false; 00813 } 00814 return true; 00815 } 00816 00821 protected function envCheckMemory() { 00822 $limit = ini_get( 'memory_limit' ); 00823 00824 if ( !$limit || $limit == -1 ) { 00825 return true; 00826 } 00827 00828 $n = wfShorthandToInteger( $limit ); 00829 00830 if( $n < $this->minMemorySize * 1024 * 1024 ) { 00831 $newLimit = "{$this->minMemorySize}M"; 00832 00833 if( ini_set( "memory_limit", $newLimit ) === false ) { 00834 $this->showMessage( 'config-memory-bad', $limit ); 00835 } else { 00836 $this->showMessage( 'config-memory-raised', $limit, $newLimit ); 00837 $this->setVar( '_RaiseMemory', true ); 00838 } 00839 } 00840 return true; 00841 } 00842 00846 protected function envCheckCache() { 00847 $caches = array(); 00848 foreach ( $this->objectCaches as $name => $function ) { 00849 if ( function_exists( $function ) ) { 00850 if ( $name == 'xcache' && !wfIniGetBool( 'xcache.var_size' ) ) { 00851 continue; 00852 } 00853 $caches[$name] = true; 00854 } 00855 } 00856 00857 if ( !$caches ) { 00858 $this->showMessage( 'config-no-cache' ); 00859 } 00860 00861 $this->setVar( '_Caches', $caches ); 00862 } 00863 00868 protected function envCheckModSecurity() { 00869 if ( self::apacheModulePresent( 'mod_security' ) ) { 00870 $this->showMessage( 'config-mod-security' ); 00871 } 00872 return true; 00873 } 00874 00879 protected function envCheckDiff3() { 00880 $names = array( "gdiff3", "diff3", "diff3.exe" ); 00881 $versionInfo = array( '$1 --version 2>&1', 'GNU diffutils' ); 00882 00883 $diff3 = self::locateExecutableInDefaultPaths( $names, $versionInfo ); 00884 00885 if ( $diff3 ) { 00886 $this->setVar( 'wgDiff3', $diff3 ); 00887 } else { 00888 $this->setVar( 'wgDiff3', false ); 00889 $this->showMessage( 'config-diff3-bad' ); 00890 } 00891 return true; 00892 } 00893 00898 protected function envCheckGraphics() { 00899 $names = array( wfIsWindows() ? 'convert.exe' : 'convert' ); 00900 $convert = self::locateExecutableInDefaultPaths( $names, array( '$1 -version', 'ImageMagick' ) ); 00901 00902 $this->setVar( 'wgImageMagickConvertCommand', '' ); 00903 if ( $convert ) { 00904 $this->setVar( 'wgImageMagickConvertCommand', $convert ); 00905 $this->showMessage( 'config-imagemagick', $convert ); 00906 return true; 00907 } elseif ( function_exists( 'imagejpeg' ) ) { 00908 $this->showMessage( 'config-gd' ); 00909 00910 } else { 00911 $this->showMessage( 'config-no-scaling' ); 00912 } 00913 return true; 00914 } 00915 00919 protected function envCheckServer() { 00920 $server = $this->envGetDefaultServer(); 00921 $this->showMessage( 'config-using-server', $server ); 00922 $this->setVar( 'wgServer', $server ); 00923 return true; 00924 } 00925 00930 protected abstract function envGetDefaultServer(); 00931 00936 protected function envCheckPath() { 00937 global $IP; 00938 $IP = dirname( dirname( __DIR__ ) ); 00939 $this->setVar( 'IP', $IP ); 00940 00941 $this->showMessage( 'config-using-uri', $this->getVar( 'wgServer' ), $this->getVar( 'wgScriptPath' ) ); 00942 return true; 00943 } 00944 00948 protected function envCheckExtension() { 00949 // @todo FIXME: Detect this properly 00950 if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) { 00951 $ext = 'php5'; 00952 } else { 00953 $ext = 'php'; 00954 } 00955 $this->setVar( 'wgScriptExtension', ".$ext" ); 00956 return true; 00957 } 00958 00963 protected function envCheckShellLocale() { 00964 $os = php_uname( 's' ); 00965 $supported = array( 'Linux', 'SunOS', 'HP-UX', 'Darwin' ); # Tested these 00966 00967 if ( !in_array( $os, $supported ) ) { 00968 return true; 00969 } 00970 00971 # Get a list of available locales. 00972 $ret = false; 00973 $lines = wfShellExec( '/usr/bin/locale -a', $ret ); 00974 00975 if ( $ret ) { 00976 return true; 00977 } 00978 00979 $lines = wfArrayMap( 'trim', explode( "\n", $lines ) ); 00980 $candidatesByLocale = array(); 00981 $candidatesByLang = array(); 00982 00983 foreach ( $lines as $line ) { 00984 if ( $line === '' ) { 00985 continue; 00986 } 00987 00988 if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) { 00989 continue; 00990 } 00991 00992 list( $all, $lang, $territory, $charset, $modifier ) = $m; 00993 00994 $candidatesByLocale[$m[0]] = $m; 00995 $candidatesByLang[$lang][] = $m; 00996 } 00997 00998 # Try the current value of LANG. 00999 if ( isset( $candidatesByLocale[ getenv( 'LANG' ) ] ) ) { 01000 $this->setVar( 'wgShellLocale', getenv( 'LANG' ) ); 01001 return true; 01002 } 01003 01004 # Try the most common ones. 01005 $commonLocales = array( 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' ); 01006 foreach ( $commonLocales as $commonLocale ) { 01007 if ( isset( $candidatesByLocale[$commonLocale] ) ) { 01008 $this->setVar( 'wgShellLocale', $commonLocale ); 01009 return true; 01010 } 01011 } 01012 01013 # Is there an available locale in the Wiki's language? 01014 $wikiLang = $this->getVar( 'wgLanguageCode' ); 01015 01016 if ( isset( $candidatesByLang[$wikiLang] ) ) { 01017 $m = reset( $candidatesByLang[$wikiLang] ); 01018 $this->setVar( 'wgShellLocale', $m[0] ); 01019 return true; 01020 } 01021 01022 # Are there any at all? 01023 if ( count( $candidatesByLocale ) ) { 01024 $m = reset( $candidatesByLocale ); 01025 $this->setVar( 'wgShellLocale', $m[0] ); 01026 return true; 01027 } 01028 01029 # Give up. 01030 return true; 01031 } 01032 01037 protected function envCheckUploadsDirectory() { 01038 global $IP; 01039 01040 $dir = $IP . '/images/'; 01041 $url = $this->getVar( 'wgServer' ) . $this->getVar( 'wgScriptPath' ) . '/images/'; 01042 $safe = !$this->dirIsExecutable( $dir, $url ); 01043 01044 if ( !$safe ) { 01045 $this->showMessage( 'config-uploads-not-safe', $dir ); 01046 } 01047 return true; 01048 } 01049 01056 protected function envCheckSuhosinMaxValueLength() { 01057 $maxValueLength = ini_get( 'suhosin.get.max_value_length' ); 01058 if ( $maxValueLength > 0 ) { 01059 if( $maxValueLength < 1024 ) { 01060 # Only warn if the value is below the sane 1024 01061 $this->showMessage( 'config-suhosin-max-value-length', $maxValueLength ); 01062 } 01063 } else { 01064 $maxValueLength = -1; 01065 } 01066 $this->setVar( 'wgResourceLoaderMaxQueryLength', $maxValueLength ); 01067 return true; 01068 } 01069 01075 protected function unicodeChar( $c ) { 01076 $c = hexdec($c); 01077 if ($c <= 0x7F) { 01078 return chr($c); 01079 } elseif ($c <= 0x7FF) { 01080 return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F); 01081 } elseif ($c <= 0xFFFF) { 01082 return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F) 01083 . chr(0x80 | $c & 0x3F); 01084 } elseif ($c <= 0x10FFFF) { 01085 return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F) 01086 . chr(0x80 | $c >> 6 & 0x3F) 01087 . chr(0x80 | $c & 0x3F); 01088 } else { 01089 return false; 01090 } 01091 } 01092 01093 01097 protected function envCheckLibicu() { 01098 $utf8 = function_exists( 'utf8_normalize' ); 01099 $intl = function_exists( 'normalizer_normalize' ); 01100 01108 $not_normal_c = $this->unicodeChar("FA6C"); 01109 $normal_c = $this->unicodeChar("242EE"); 01110 01111 $useNormalizer = 'php'; 01112 $needsUpdate = false; 01113 01118 if( $utf8 ) { 01119 $useNormalizer = 'utf8'; 01120 $utf8 = utf8_normalize( $not_normal_c, UtfNormal::UNORM_NFC ); 01121 if ( $utf8 !== $normal_c ) { 01122 $needsUpdate = true; 01123 } 01124 } 01125 if( $intl ) { 01126 $useNormalizer = 'intl'; 01127 $intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C ); 01128 if ( $intl !== $normal_c ) { 01129 $needsUpdate = true; 01130 } 01131 } 01132 01133 // Uses messages 'config-unicode-using-php', 'config-unicode-using-utf8', 'config-unicode-using-intl' 01134 if( $useNormalizer === 'php' ) { 01135 $this->showMessage( 'config-unicode-pure-php-warning' ); 01136 } else { 01137 $this->showMessage( 'config-unicode-using-' . $useNormalizer ); 01138 if( $needsUpdate ) { 01139 $this->showMessage( 'config-unicode-update-warning' ); 01140 } 01141 } 01142 } 01143 01147 protected function envCheckCtype() { 01148 if ( !function_exists( 'ctype_digit' ) ) { 01149 $this->showError( 'config-ctype' ); 01150 return false; 01151 } 01152 return true; 01153 } 01154 01162 protected static function getPossibleBinPaths() { 01163 return array_merge( 01164 array( '/usr/bin', '/usr/local/bin', '/opt/csw/bin', 01165 '/usr/gnu/bin', '/usr/sfw/bin', '/sw/bin', '/opt/local/bin' ), 01166 explode( PATH_SEPARATOR, getenv( 'PATH' ) ) 01167 ); 01168 } 01169 01187 public static function locateExecutable( $path, $names, $versionInfo = false ) { 01188 if ( !is_array( $names ) ) { 01189 $names = array( $names ); 01190 } 01191 01192 foreach ( $names as $name ) { 01193 $command = $path . DIRECTORY_SEPARATOR . $name; 01194 01195 wfSuppressWarnings(); 01196 $file_exists = file_exists( $command ); 01197 wfRestoreWarnings(); 01198 01199 if ( $file_exists ) { 01200 if ( !$versionInfo ) { 01201 return $command; 01202 } 01203 01204 $file = str_replace( '$1', wfEscapeShellArg( $command ), $versionInfo[0] ); 01205 if ( strstr( wfShellExec( $file ), $versionInfo[1] ) !== false ) { 01206 return $command; 01207 } 01208 } 01209 } 01210 return false; 01211 } 01212 01220 public static function locateExecutableInDefaultPaths( $names, $versionInfo = false ) { 01221 foreach( self::getPossibleBinPaths() as $path ) { 01222 $exe = self::locateExecutable( $path, $names, $versionInfo ); 01223 if( $exe !== false ) { 01224 return $exe; 01225 } 01226 } 01227 return false; 01228 } 01229 01238 public function dirIsExecutable( $dir, $url ) { 01239 $scriptTypes = array( 01240 'php' => array( 01241 "<?php echo 'ex' . 'ec';", 01242 "#!/var/env php5\n<?php echo 'ex' . 'ec';", 01243 ), 01244 ); 01245 01246 // it would be good to check other popular languages here, but it'll be slow. 01247 01248 wfSuppressWarnings(); 01249 01250 foreach ( $scriptTypes as $ext => $contents ) { 01251 foreach ( $contents as $source ) { 01252 $file = 'exectest.' . $ext; 01253 01254 if ( !file_put_contents( $dir . $file, $source ) ) { 01255 break; 01256 } 01257 01258 try { 01259 $text = Http::get( $url . $file, array( 'timeout' => 3 ) ); 01260 } 01261 catch( MWException $e ) { 01262 // Http::get throws with allow_url_fopen = false and no curl extension. 01263 $text = null; 01264 } 01265 unlink( $dir . $file ); 01266 01267 if ( $text == 'exec' ) { 01268 wfRestoreWarnings(); 01269 return $ext; 01270 } 01271 } 01272 } 01273 01274 wfRestoreWarnings(); 01275 01276 return false; 01277 } 01278 01285 public static function apacheModulePresent( $moduleName ) { 01286 if ( function_exists( 'apache_get_modules' ) && in_array( $moduleName, apache_get_modules() ) ) { 01287 return true; 01288 } 01289 // try it the hard way 01290 ob_start(); 01291 phpinfo( INFO_MODULES ); 01292 $info = ob_get_clean(); 01293 return strpos( $info, $moduleName ) !== false; 01294 } 01295 01301 public function setParserLanguage( $lang ) { 01302 $this->parserOptions->setTargetLanguage( $lang ); 01303 $this->parserOptions->setUserLang( $lang ); 01304 } 01305 01311 protected function getDocUrl( $page ) { 01312 return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page ); 01313 } 01314 01321 public function findExtensions() { 01322 if( $this->getVar( 'IP' ) === null ) { 01323 return false; 01324 } 01325 01326 $exts = array(); 01327 $extDir = $this->getVar( 'IP' ) . '/extensions'; 01328 $dh = opendir( $extDir ); 01329 01330 while ( ( $file = readdir( $dh ) ) !== false ) { 01331 if( !is_dir( "$extDir/$file" ) ) { 01332 continue; 01333 } 01334 if( file_exists( "$extDir/$file/$file.php" ) ) { 01335 $exts[] = $file; 01336 } 01337 } 01338 natcasesort( $exts ); 01339 01340 return $exts; 01341 } 01342 01348 protected function includeExtensions() { 01349 global $IP; 01350 $exts = $this->getVar( '_Extensions' ); 01351 $IP = $this->getVar( 'IP' ); 01352 01361 global $wgAutoloadClasses; 01362 $wgAutoloadClasses = array(); 01363 01364 require( "$IP/includes/DefaultSettings.php" ); 01365 01366 foreach( $exts as $e ) { 01367 require_once( "$IP/extensions/$e/$e.php" ); 01368 } 01369 01370 $hooksWeWant = isset( $wgHooks['LoadExtensionSchemaUpdates'] ) ? 01371 $wgHooks['LoadExtensionSchemaUpdates'] : array(); 01372 01373 // Unset everyone else's hooks. Lord knows what someone might be doing 01374 // in ParserFirstCallInit (see bug 27171) 01375 $GLOBALS['wgHooks'] = array( 'LoadExtensionSchemaUpdates' => $hooksWeWant ); 01376 01377 return Status::newGood(); 01378 } 01379 01392 protected function getInstallSteps( DatabaseInstaller $installer ) { 01393 $coreInstallSteps = array( 01394 array( 'name' => 'database', 'callback' => array( $installer, 'setupDatabase' ) ), 01395 array( 'name' => 'tables', 'callback' => array( $installer, 'createTables' ) ), 01396 array( 'name' => 'interwiki', 'callback' => array( $installer, 'populateInterwikiTable' ) ), 01397 array( 'name' => 'stats', 'callback' => array( $this, 'populateSiteStats' ) ), 01398 array( 'name' => 'keys', 'callback' => array( $this, 'generateKeys' ) ), 01399 array( 'name' => 'sysop', 'callback' => array( $this, 'createSysop' ) ), 01400 array( 'name' => 'mainpage', 'callback' => array( $this, 'createMainpage' ) ), 01401 ); 01402 01403 // Build the array of install steps starting from the core install list, 01404 // then adding any callbacks that wanted to attach after a given step 01405 foreach( $coreInstallSteps as $step ) { 01406 $this->installSteps[] = $step; 01407 if( isset( $this->extraInstallSteps[ $step['name'] ] ) ) { 01408 $this->installSteps = array_merge( 01409 $this->installSteps, 01410 $this->extraInstallSteps[ $step['name'] ] 01411 ); 01412 } 01413 } 01414 01415 // Prepend any steps that want to be at the beginning 01416 if( isset( $this->extraInstallSteps['BEGINNING'] ) ) { 01417 $this->installSteps = array_merge( 01418 $this->extraInstallSteps['BEGINNING'], 01419 $this->installSteps 01420 ); 01421 } 01422 01423 // Extensions should always go first, chance to tie into hooks and such 01424 if( count( $this->getVar( '_Extensions' ) ) ) { 01425 array_unshift( $this->installSteps, 01426 array( 'name' => 'extensions', 'callback' => array( $this, 'includeExtensions' ) ) 01427 ); 01428 $this->installSteps[] = array( 01429 'name' => 'extension-tables', 01430 'callback' => array( $installer, 'createExtensionTables' ) 01431 ); 01432 } 01433 return $this->installSteps; 01434 } 01435 01444 public function performInstallation( $startCB, $endCB ) { 01445 $installResults = array(); 01446 $installer = $this->getDBInstaller(); 01447 $installer->preInstall(); 01448 $steps = $this->getInstallSteps( $installer ); 01449 foreach( $steps as $stepObj ) { 01450 $name = $stepObj['name']; 01451 call_user_func_array( $startCB, array( $name ) ); 01452 01453 // Perform the callback step 01454 $status = call_user_func( $stepObj['callback'], $installer ); 01455 01456 // Output and save the results 01457 call_user_func( $endCB, $name, $status ); 01458 $installResults[$name] = $status; 01459 01460 // If we've hit some sort of fatal, we need to bail. 01461 // Callback already had a chance to do output above. 01462 if( !$status->isOk() ) { 01463 break; 01464 } 01465 } 01466 if( $status->isOk() ) { 01467 $this->setVar( '_InstallDone', true ); 01468 } 01469 return $installResults; 01470 } 01471 01477 public function generateKeys() { 01478 $keys = array( 'wgSecretKey' => 64 ); 01479 if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) { 01480 $keys['wgUpgradeKey'] = 16; 01481 } 01482 return $this->doGenerateKeys( $keys ); 01483 } 01484 01492 protected function doGenerateKeys( $keys ) { 01493 $status = Status::newGood(); 01494 01495 $strong = true; 01496 foreach ( $keys as $name => $length ) { 01497 $secretKey = MWCryptRand::generateHex( $length, true ); 01498 if ( !MWCryptRand::wasStrong() ) { 01499 $strong = false; 01500 } 01501 01502 $this->setVar( $name, $secretKey ); 01503 } 01504 01505 if ( !$strong ) { 01506 $names = array_keys( $keys ); 01507 $names = preg_replace( '/^(.*)$/', '\$$1', $names ); 01508 global $wgLang; 01509 $status->warning( 'config-insecure-keys', $wgLang->listToText( $names ), count( $names ) ); 01510 } 01511 01512 return $status; 01513 } 01514 01520 protected function createSysop() { 01521 $name = $this->getVar( '_AdminName' ); 01522 $user = User::newFromName( $name ); 01523 01524 if ( !$user ) { 01525 // We should've validated this earlier anyway! 01526 return Status::newFatal( 'config-admin-error-user', $name ); 01527 } 01528 01529 if ( $user->idForName() == 0 ) { 01530 $user->addToDatabase(); 01531 01532 try { 01533 $user->setPassword( $this->getVar( '_AdminPassword' ) ); 01534 } catch( PasswordError $pwe ) { 01535 return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() ); 01536 } 01537 01538 $user->addGroup( 'sysop' ); 01539 $user->addGroup( 'bureaucrat' ); 01540 if( $this->getVar( '_AdminEmail' ) ) { 01541 $user->setEmail( $this->getVar( '_AdminEmail' ) ); 01542 } 01543 $user->saveSettings(); 01544 01545 // Update user count 01546 $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 ); 01547 $ssUpdate->doUpdate(); 01548 } 01549 $status = Status::newGood(); 01550 01551 if( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) { 01552 $this->subscribeToMediaWikiAnnounce( $status ); 01553 } 01554 01555 return $status; 01556 } 01557 01561 private function subscribeToMediaWikiAnnounce( Status $s ) { 01562 $params = array( 01563 'email' => $this->getVar( '_AdminEmail' ), 01564 'language' => 'en', 01565 'digest' => 0 01566 ); 01567 01568 // Mailman doesn't support as many languages as we do, so check to make 01569 // sure their selected language is available 01570 $myLang = $this->getVar( '_UserLang' ); 01571 if( in_array( $myLang, $this->mediaWikiAnnounceLanguages ) ) { 01572 $myLang = $myLang == 'pt-br' ? 'pt_BR' : $myLang; // rewrite to Mailman's pt_BR 01573 $params['language'] = $myLang; 01574 } 01575 01576 if( MWHttpRequest::canMakeRequests() ) { 01577 $res = MWHttpRequest::factory( $this->mediaWikiAnnounceUrl, 01578 array( 'method' => 'POST', 'postData' => $params ) )->execute(); 01579 if( !$res->isOK() ) { 01580 $s->warning( 'config-install-subscribe-fail', $res->getMessage() ); 01581 } 01582 } else { 01583 $s->warning( 'config-install-subscribe-notpossible' ); 01584 } 01585 } 01586 01593 protected function createMainpage( DatabaseInstaller $installer ) { 01594 $status = Status::newGood(); 01595 try { 01596 $page = WikiPage::factory( Title::newMainPage() ); 01597 $page->doEdit( wfMessage( 'mainpagetext' )->inContentLanguage()->text() . "\n\n" . 01598 wfMessage( 'mainpagedocfooter' )->inContentLanguage()->text(), 01599 '', 01600 EDIT_NEW, 01601 false, 01602 User::newFromName( 'MediaWiki default' ) 01603 ); 01604 } catch (MWException $e) { 01605 //using raw, because $wgShowExceptionDetails can not be set yet 01606 $status->fatal( 'config-install-mainpage-failed', $e->getMessage() ); 01607 } 01608 01609 return $status; 01610 } 01611 01615 public static function overrideConfig() { 01616 define( 'MW_NO_SESSION', 1 ); 01617 01618 // Don't access the database 01619 $GLOBALS['wgUseDatabaseMessages'] = false; 01620 // Don't cache langconv tables 01621 $GLOBALS['wgLanguageConverterCacheType'] = CACHE_NONE; 01622 // Debug-friendly 01623 $GLOBALS['wgShowExceptionDetails'] = true; 01624 // Don't break forms 01625 $GLOBALS['wgExternalLinkTarget'] = '_blank'; 01626 01627 // Extended debugging 01628 $GLOBALS['wgShowSQLErrors'] = true; 01629 $GLOBALS['wgShowDBErrorBacktrace'] = true; 01630 01631 // Allow multiple ob_flush() calls 01632 $GLOBALS['wgDisableOutputCompression'] = true; 01633 01634 // Use a sensible cookie prefix (not my_wiki) 01635 $GLOBALS['wgCookiePrefix'] = 'mw_installer'; 01636 01637 // Some of the environment checks make shell requests, remove limits 01638 $GLOBALS['wgMaxShellMemory'] = 0; 01639 } 01640 01648 public function addInstallStep( $callback, $findStep = 'BEGINNING' ) { 01649 $this->extraInstallSteps[$findStep][] = $callback; 01650 } 01651 01656 protected function disableTimeLimit() { 01657 wfSuppressWarnings(); 01658 set_time_limit( 0 ); 01659 wfRestoreWarnings(); 01660 } 01661 }