MediaWiki
REL1_23
|
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 'mssql', 00106 'sqlite', 00107 ); 00108 00116 protected $envChecks = array( 00117 'envCheckDB', 00118 'envCheckRegisterGlobals', 00119 'envCheckBrokenXML', 00120 'envCheckMagicQuotes', 00121 'envCheckMagicSybase', 00122 'envCheckMbstring', 00123 'envCheckSafeMode', 00124 'envCheckXML', 00125 'envCheckPCRE', 00126 'envCheckMemory', 00127 'envCheckCache', 00128 'envCheckModSecurity', 00129 'envCheckDiff3', 00130 'envCheckGraphics', 00131 'envCheckGit', 00132 'envCheckServer', 00133 'envCheckPath', 00134 'envCheckExtension', 00135 'envCheckShellLocale', 00136 'envCheckUploadsDirectory', 00137 'envCheckLibicu', 00138 'envCheckSuhosinMaxValueLength', 00139 'envCheckCtype', 00140 'envCheckJSON', 00141 ); 00142 00150 protected $defaultVarNames = array( 00151 'wgSitename', 00152 'wgPasswordSender', 00153 'wgLanguageCode', 00154 'wgRightsIcon', 00155 'wgRightsText', 00156 'wgRightsUrl', 00157 'wgMainCacheType', 00158 'wgEnableEmail', 00159 'wgEnableUserEmail', 00160 'wgEnotifUserTalk', 00161 'wgEnotifWatchlist', 00162 'wgEmailAuthentication', 00163 'wgDBtype', 00164 'wgDiff3', 00165 'wgImageMagickConvertCommand', 00166 'wgGitBin', 00167 'IP', 00168 'wgScriptPath', 00169 'wgScriptExtension', 00170 'wgMetaNamespace', 00171 'wgDeletedDirectory', 00172 'wgEnableUploads', 00173 'wgShellLocale', 00174 'wgSecretKey', 00175 'wgUseInstantCommons', 00176 'wgUpgradeKey', 00177 'wgDefaultSkin', 00178 'wgResourceLoaderMaxQueryLength', 00179 ); 00180 00188 protected $internalDefaults = array( 00189 '_UserLang' => 'en', 00190 '_Environment' => false, 00191 '_SafeMode' => false, 00192 '_RaiseMemory' => false, 00193 '_UpgradeDone' => false, 00194 '_InstallDone' => false, 00195 '_Caches' => array(), 00196 '_InstallPassword' => '', 00197 '_SameAccount' => true, 00198 '_CreateDBAccount' => false, 00199 '_NamespaceType' => 'site-name', 00200 '_AdminName' => '', // will be set later, when the user selects language 00201 '_AdminPassword' => '', 00202 '_AdminPassword2' => '', 00203 '_AdminEmail' => '', 00204 '_Subscribe' => false, 00205 '_SkipOptional' => 'continue', 00206 '_RightsProfile' => 'wiki', 00207 '_LicenseCode' => 'none', 00208 '_CCDone' => false, 00209 '_Extensions' => array(), 00210 '_MemCachedServers' => '', 00211 '_UpgradeKeySupplied' => false, 00212 '_ExistingDBSettings' => false, 00213 00214 // $wgLogo is probably wrong (bug 48084); set something that will work. 00215 // Single quotes work fine here, as LocalSettingsGenerator outputs this unescaped. 00216 'wgLogo' => '$wgStylePath/common/images/wiki.png', 00217 ); 00218 00224 private $installSteps = array(); 00225 00231 protected $extraInstallSteps = array(); 00232 00238 protected $objectCaches = array( 00239 'xcache' => 'xcache_get', 00240 'apc' => 'apc_fetch', 00241 'wincache' => 'wincache_ucache_get' 00242 ); 00243 00249 public $rightsProfiles = array( 00250 'wiki' => array(), 00251 'no-anon' => array( 00252 '*' => array( 'edit' => false ) 00253 ), 00254 'fishbowl' => array( 00255 '*' => array( 00256 'createaccount' => false, 00257 'edit' => false, 00258 ), 00259 ), 00260 'private' => array( 00261 '*' => array( 00262 'createaccount' => false, 00263 'edit' => false, 00264 'read' => false, 00265 ), 00266 ), 00267 ); 00268 00274 public $licenses = array( 00275 'cc-by' => array( 00276 'url' => 'http://creativecommons.org/licenses/by/3.0/', 00277 'icon' => '{$wgStylePath}/common/images/cc-by.png', 00278 ), 00279 'cc-by-sa' => array( 00280 'url' => 'http://creativecommons.org/licenses/by-sa/3.0/', 00281 'icon' => '{$wgStylePath}/common/images/cc-by-sa.png', 00282 ), 00283 'cc-by-nc-sa' => array( 00284 'url' => 'http://creativecommons.org/licenses/by-nc-sa/3.0/', 00285 'icon' => '{$wgStylePath}/common/images/cc-by-nc-sa.png', 00286 ), 00287 'cc-0' => array( 00288 'url' => 'https://creativecommons.org/publicdomain/zero/1.0/', 00289 'icon' => '{$wgStylePath}/common/images/cc-0.png', 00290 ), 00291 'pd' => array( 00292 'url' => '', 00293 'icon' => '{$wgStylePath}/common/images/public-domain.png', 00294 ), 00295 'gfdl' => array( 00296 'url' => 'http://www.gnu.org/copyleft/fdl.html', 00297 'icon' => '{$wgStylePath}/common/images/gnu-fdl.png', 00298 ), 00299 'none' => array( 00300 'url' => '', 00301 'icon' => '', 00302 'text' => '' 00303 ), 00304 'cc-choose' => array( 00305 // Details will be filled in by the selector. 00306 'url' => '', 00307 'icon' => '', 00308 'text' => '', 00309 ), 00310 ); 00311 00315 protected $mediaWikiAnnounceUrl = 00316 'https://lists.wikimedia.org/mailman/subscribe/mediawiki-announce'; 00317 00321 protected $mediaWikiAnnounceLanguages = array( 00322 'ca', 'cs', 'da', 'de', 'en', 'es', 'et', 'eu', 'fi', 'fr', 'hr', 'hu', 00323 'it', 'ja', 'ko', 'lt', 'nl', 'no', 'pl', 'pt', 'pt-br', 'ro', 'ru', 00324 'sl', 'sr', 'sv', 'tr', 'uk' 00325 ); 00326 00334 abstract public function showMessage( $msg /*, ... */ ); 00335 00340 abstract public function showError( $msg /*, ... */ ); 00341 00346 abstract public function showStatusMessage( Status $status ); 00347 00351 public function __construct() { 00352 global $wgMessagesDirs, $wgUser; 00353 00354 // Disable the i18n cache and LoadBalancer 00355 Language::getLocalisationCache()->disableBackend(); 00356 LBFactory::disableBackend(); 00357 00358 // Load the installer's i18n. 00359 $wgMessagesDirs['MediawikiInstaller'] = __DIR__ . '/i18n'; 00360 00361 // Having a user with id = 0 safeguards us from DB access via User::loadOptions(). 00362 $wgUser = User::newFromId( 0 ); 00363 00364 $this->settings = $this->internalDefaults; 00365 00366 foreach ( $this->defaultVarNames as $var ) { 00367 $this->settings[$var] = $GLOBALS[$var]; 00368 } 00369 00370 $compiledDBs = array(); 00371 foreach ( self::getDBTypes() as $type ) { 00372 $installer = $this->getDBInstaller( $type ); 00373 00374 if ( !$installer->isCompiled() ) { 00375 continue; 00376 } 00377 $compiledDBs[] = $type; 00378 00379 $defaults = $installer->getGlobalDefaults(); 00380 00381 foreach ( $installer->getGlobalNames() as $var ) { 00382 if ( isset( $defaults[$var] ) ) { 00383 $this->settings[$var] = $defaults[$var]; 00384 } else { 00385 $this->settings[$var] = $GLOBALS[$var]; 00386 } 00387 } 00388 } 00389 $this->compiledDBs = $compiledDBs; 00390 00391 $this->parserTitle = Title::newFromText( 'Installer' ); 00392 $this->parserOptions = new ParserOptions; // language will be wrong :( 00393 $this->parserOptions->setEditSection( false ); 00394 } 00395 00401 public static function getDBTypes() { 00402 return self::$dbTypes; 00403 } 00404 00418 public function doEnvironmentChecks() { 00419 $phpVersion = phpversion(); 00420 if ( version_compare( $phpVersion, self::MINIMUM_PHP_VERSION, '>=' ) ) { 00421 $this->showMessage( 'config-env-php', $phpVersion ); 00422 $good = true; 00423 } else { 00424 $this->showMessage( 'config-env-php-toolow', $phpVersion, self::MINIMUM_PHP_VERSION ); 00425 $good = false; 00426 } 00427 00428 // Must go here because an old version of PCRE can prevent other checks from completing 00429 if ( $good ) { 00430 list( $pcreVersion ) = explode( ' ', PCRE_VERSION, 2 ); 00431 if ( version_compare( $pcreVersion, self::MINIMUM_PCRE_VERSION, '<' ) ) { 00432 $this->showError( 'config-pcre-old', self::MINIMUM_PCRE_VERSION, $pcreVersion ); 00433 $good = false; 00434 } 00435 } 00436 00437 if ( $good ) { 00438 foreach ( $this->envChecks as $check ) { 00439 $status = $this->$check(); 00440 if ( $status === false ) { 00441 $good = false; 00442 } 00443 } 00444 } 00445 00446 $this->setVar( '_Environment', $good ); 00447 00448 return $good ? Status::newGood() : Status::newFatal( 'config-env-bad' ); 00449 } 00450 00457 public function setVar( $name, $value ) { 00458 $this->settings[$name] = $value; 00459 } 00460 00471 public function getVar( $name, $default = null ) { 00472 if ( !isset( $this->settings[$name] ) ) { 00473 return $default; 00474 } else { 00475 return $this->settings[$name]; 00476 } 00477 } 00478 00484 public function getCompiledDBs() { 00485 return $this->compiledDBs; 00486 } 00487 00495 public function getDBInstaller( $type = false ) { 00496 if ( !$type ) { 00497 $type = $this->getVar( 'wgDBtype' ); 00498 } 00499 00500 $type = strtolower( $type ); 00501 00502 if ( !isset( $this->dbInstallers[$type] ) ) { 00503 $class = ucfirst( $type ) . 'Installer'; 00504 $this->dbInstallers[$type] = new $class( $this ); 00505 } 00506 00507 return $this->dbInstallers[$type]; 00508 } 00509 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 00530 return get_defined_vars(); 00531 } 00532 00542 public function getFakePassword( $realPassword ) { 00543 return str_repeat( '*', strlen( $realPassword ) ); 00544 } 00545 00553 public function setPassword( $name, $value ) { 00554 if ( !preg_match( '/^\*+$/', $value ) ) { 00555 $this->setVar( $name, $value ); 00556 } 00557 } 00558 00570 public static function maybeGetWebserverPrimaryGroup() { 00571 if ( !function_exists( 'posix_getegid' ) || !function_exists( 'posix_getpwuid' ) ) { 00572 # I don't know this, this isn't UNIX. 00573 return null; 00574 } 00575 00576 # posix_getegid() *not* getmygid() because we want the group of the webserver, 00577 # not whoever owns the current script. 00578 $gid = posix_getegid(); 00579 $getpwuid = posix_getpwuid( $gid ); 00580 $group = $getpwuid['name']; 00581 00582 return $group; 00583 } 00584 00601 public function parse( $text, $lineStart = false ) { 00602 global $wgParser; 00603 00604 try { 00605 $out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart ); 00606 $html = $out->getText(); 00607 } catch ( DBAccessError $e ) { 00608 $html = '<!--DB access attempted during parse--> ' . htmlspecialchars( $text ); 00609 00610 if ( !empty( $this->debug ) ) { 00611 $html .= "<!--\n" . $e->getTraceAsString() . "\n-->"; 00612 } 00613 } 00614 00615 return $html; 00616 } 00617 00621 public function getParserOptions() { 00622 return $this->parserOptions; 00623 } 00624 00625 public function disableLinkPopups() { 00626 $this->parserOptions->setExternalLinkTarget( false ); 00627 } 00628 00629 public function restoreLinkPopups() { 00630 global $wgExternalLinkTarget; 00631 $this->parserOptions->setExternalLinkTarget( $wgExternalLinkTarget ); 00632 } 00633 00642 public function populateSiteStats( DatabaseInstaller $installer ) { 00643 $status = $installer->getConnection(); 00644 if ( !$status->isOK() ) { 00645 return $status; 00646 } 00647 $status->value->insert( 00648 'site_stats', 00649 array( 00650 'ss_row_id' => 1, 00651 'ss_total_views' => 0, 00652 'ss_total_edits' => 0, 00653 'ss_good_articles' => 0, 00654 'ss_total_pages' => 0, 00655 'ss_users' => 0, 00656 'ss_images' => 0 00657 ), 00658 __METHOD__, 'IGNORE' 00659 ); 00660 00661 return Status::newGood(); 00662 } 00663 00667 public function exportVars() { 00668 foreach ( $this->settings as $name => $value ) { 00669 if ( substr( $name, 0, 2 ) == 'wg' ) { 00670 $GLOBALS[$name] = $value; 00671 } 00672 } 00673 } 00674 00679 protected function envCheckDB() { 00680 global $wgLang; 00681 00682 $allNames = array(); 00683 00684 // Messages: config-type-mysql, config-type-postgres, config-type-oracle, 00685 // config-type-sqlite 00686 foreach ( self::getDBTypes() as $name ) { 00687 $allNames[] = wfMessage( "config-type-$name" )->text(); 00688 } 00689 00690 $databases = $this->getCompiledDBs(); 00691 00692 $databases = array_flip( $databases ); 00693 foreach ( array_keys( $databases ) as $db ) { 00694 $installer = $this->getDBInstaller( $db ); 00695 $status = $installer->checkPrerequisites(); 00696 if ( !$status->isGood() ) { 00697 $this->showStatusMessage( $status ); 00698 } 00699 if ( !$status->isOK() ) { 00700 unset( $databases[$db] ); 00701 } 00702 } 00703 $databases = array_flip( $databases ); 00704 if ( !$databases ) { 00705 $this->showError( 'config-no-db', $wgLang->commaList( $allNames ) ); 00706 00707 // @todo FIXME: This only works for the web installer! 00708 return false; 00709 } 00710 00711 return true; 00712 } 00713 00717 protected function envCheckRegisterGlobals() { 00718 if ( wfIniGetBool( 'register_globals' ) ) { 00719 $this->showMessage( 'config-register-globals' ); 00720 } 00721 } 00722 00727 protected function envCheckBrokenXML() { 00728 $test = new PhpXmlBugTester(); 00729 if ( !$test->ok ) { 00730 $this->showError( 'config-brokenlibxml' ); 00731 00732 return false; 00733 } 00734 00735 return true; 00736 } 00737 00742 protected function envCheckMagicQuotes() { 00743 if ( wfIniGetBool( "magic_quotes_runtime" ) ) { 00744 $this->showError( 'config-magic-quotes-runtime' ); 00745 00746 return false; 00747 } 00748 00749 return true; 00750 } 00751 00756 protected function envCheckMagicSybase() { 00757 if ( wfIniGetBool( 'magic_quotes_sybase' ) ) { 00758 $this->showError( 'config-magic-quotes-sybase' ); 00759 00760 return false; 00761 } 00762 00763 return true; 00764 } 00765 00770 protected function envCheckMbstring() { 00771 if ( wfIniGetBool( 'mbstring.func_overload' ) ) { 00772 $this->showError( 'config-mbstring' ); 00773 00774 return false; 00775 } 00776 00777 return true; 00778 } 00779 00784 protected function envCheckSafeMode() { 00785 if ( wfIniGetBool( 'safe_mode' ) ) { 00786 $this->setVar( '_SafeMode', true ); 00787 $this->showMessage( 'config-safe-mode' ); 00788 } 00789 00790 return true; 00791 } 00792 00797 protected function envCheckXML() { 00798 if ( !function_exists( "utf8_encode" ) ) { 00799 $this->showError( 'config-xml-bad' ); 00800 00801 return false; 00802 } 00803 00804 return true; 00805 } 00806 00815 protected function envCheckPCRE() { 00816 wfSuppressWarnings(); 00817 $regexd = preg_replace( '/[\x{0430}-\x{04FF}]/iu', '', '-АБВГД-' ); 00818 // Need to check for \p support too, as PCRE can be compiled 00819 // with utf8 support, but not unicode property support. 00820 // check that \p{Zs} (space separators) matches 00821 // U+3000 (Ideographic space) 00822 $regexprop = preg_replace( '/\p{Zs}/u', '', "-\xE3\x80\x80-" ); 00823 wfRestoreWarnings(); 00824 if ( $regexd != '--' || $regexprop != '--' ) { 00825 $this->showError( 'config-pcre-no-utf8' ); 00826 00827 return false; 00828 } 00829 00830 return true; 00831 } 00832 00837 protected function envCheckMemory() { 00838 $limit = ini_get( 'memory_limit' ); 00839 00840 if ( !$limit || $limit == -1 ) { 00841 return true; 00842 } 00843 00844 $n = wfShorthandToInteger( $limit ); 00845 00846 if ( $n < $this->minMemorySize * 1024 * 1024 ) { 00847 $newLimit = "{$this->minMemorySize}M"; 00848 00849 if ( ini_set( "memory_limit", $newLimit ) === false ) { 00850 $this->showMessage( 'config-memory-bad', $limit ); 00851 } else { 00852 $this->showMessage( 'config-memory-raised', $limit, $newLimit ); 00853 $this->setVar( '_RaiseMemory', true ); 00854 } 00855 } 00856 00857 return true; 00858 } 00859 00863 protected function envCheckCache() { 00864 $caches = array(); 00865 foreach ( $this->objectCaches as $name => $function ) { 00866 if ( function_exists( $function ) ) { 00867 if ( $name == 'xcache' && !wfIniGetBool( 'xcache.var_size' ) ) { 00868 continue; 00869 } 00870 $caches[$name] = true; 00871 } 00872 } 00873 00874 if ( !$caches ) { 00875 $this->showMessage( 'config-no-cache' ); 00876 } 00877 00878 $this->setVar( '_Caches', $caches ); 00879 } 00880 00885 protected function envCheckModSecurity() { 00886 if ( self::apacheModulePresent( 'mod_security' ) ) { 00887 $this->showMessage( 'config-mod-security' ); 00888 } 00889 00890 return true; 00891 } 00892 00897 protected function envCheckDiff3() { 00898 $names = array( "gdiff3", "diff3", "diff3.exe" ); 00899 $versionInfo = array( '$1 --version 2>&1', 'GNU diffutils' ); 00900 00901 $diff3 = self::locateExecutableInDefaultPaths( $names, $versionInfo ); 00902 00903 if ( $diff3 ) { 00904 $this->setVar( 'wgDiff3', $diff3 ); 00905 } else { 00906 $this->setVar( 'wgDiff3', false ); 00907 $this->showMessage( 'config-diff3-bad' ); 00908 } 00909 00910 return true; 00911 } 00912 00917 protected function envCheckGraphics() { 00918 $names = array( wfIsWindows() ? 'convert.exe' : 'convert' ); 00919 $versionInfo = array( '$1 -version', 'ImageMagick' ); 00920 $convert = self::locateExecutableInDefaultPaths( $names, $versionInfo ); 00921 00922 $this->setVar( 'wgImageMagickConvertCommand', '' ); 00923 if ( $convert ) { 00924 $this->setVar( 'wgImageMagickConvertCommand', $convert ); 00925 $this->showMessage( 'config-imagemagick', $convert ); 00926 00927 return true; 00928 } elseif ( function_exists( 'imagejpeg' ) ) { 00929 $this->showMessage( 'config-gd' ); 00930 } else { 00931 $this->showMessage( 'config-no-scaling' ); 00932 } 00933 00934 return true; 00935 } 00936 00943 protected function envCheckGit() { 00944 $names = array( wfIsWindows() ? 'git.exe' : 'git' ); 00945 $versionInfo = array( '$1 --version', 'git version' ); 00946 00947 $git = self::locateExecutableInDefaultPaths( $names, $versionInfo ); 00948 00949 if ( $git ) { 00950 $this->setVar( 'wgGitBin', $git ); 00951 $this->showMessage( 'config-git', $git ); 00952 } else { 00953 $this->setVar( 'wgGitBin', false ); 00954 $this->showMessage( 'config-git-bad' ); 00955 } 00956 00957 return true; 00958 } 00959 00963 protected function envCheckServer() { 00964 $server = $this->envGetDefaultServer(); 00965 if ( $server !== null ) { 00966 $this->showMessage( 'config-using-server', $server ); 00967 $this->setVar( 'wgServer', $server ); 00968 } 00969 00970 return true; 00971 } 00972 00977 abstract protected function envGetDefaultServer(); 00978 00983 protected function envCheckPath() { 00984 global $IP; 00985 $IP = dirname( dirname( __DIR__ ) ); 00986 $this->setVar( 'IP', $IP ); 00987 00988 $this->showMessage( 00989 'config-using-uri', 00990 $this->getVar( 'wgServer' ), 00991 $this->getVar( 'wgScriptPath' ) 00992 ); 00993 00994 return true; 00995 } 00996 01001 protected function envCheckExtension() { 01002 // @todo FIXME: Detect this properly 01003 if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) { 01004 $ext = 'php5'; 01005 } else { 01006 $ext = 'php'; 01007 } 01008 $this->setVar( 'wgScriptExtension', ".$ext" ); 01009 01010 return true; 01011 } 01012 01017 protected function envCheckShellLocale() { 01018 $os = php_uname( 's' ); 01019 $supported = array( 'Linux', 'SunOS', 'HP-UX', 'Darwin' ); # Tested these 01020 01021 if ( !in_array( $os, $supported ) ) { 01022 return true; 01023 } 01024 01025 # Get a list of available locales. 01026 $ret = false; 01027 $lines = wfShellExec( '/usr/bin/locale -a', $ret ); 01028 01029 if ( $ret ) { 01030 return true; 01031 } 01032 01033 $lines = array_map( 'trim', explode( "\n", $lines ) ); 01034 $candidatesByLocale = array(); 01035 $candidatesByLang = array(); 01036 01037 foreach ( $lines as $line ) { 01038 if ( $line === '' ) { 01039 continue; 01040 } 01041 01042 if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) { 01043 continue; 01044 } 01045 01046 list( , $lang, , , ) = $m; 01047 01048 $candidatesByLocale[$m[0]] = $m; 01049 $candidatesByLang[$lang][] = $m; 01050 } 01051 01052 # Try the current value of LANG. 01053 if ( isset( $candidatesByLocale[getenv( 'LANG' )] ) ) { 01054 $this->setVar( 'wgShellLocale', getenv( 'LANG' ) ); 01055 01056 return true; 01057 } 01058 01059 # Try the most common ones. 01060 $commonLocales = array( 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' ); 01061 foreach ( $commonLocales as $commonLocale ) { 01062 if ( isset( $candidatesByLocale[$commonLocale] ) ) { 01063 $this->setVar( 'wgShellLocale', $commonLocale ); 01064 01065 return true; 01066 } 01067 } 01068 01069 # Is there an available locale in the Wiki's language? 01070 $wikiLang = $this->getVar( 'wgLanguageCode' ); 01071 01072 if ( isset( $candidatesByLang[$wikiLang] ) ) { 01073 $m = reset( $candidatesByLang[$wikiLang] ); 01074 $this->setVar( 'wgShellLocale', $m[0] ); 01075 01076 return true; 01077 } 01078 01079 # Are there any at all? 01080 if ( count( $candidatesByLocale ) ) { 01081 $m = reset( $candidatesByLocale ); 01082 $this->setVar( 'wgShellLocale', $m[0] ); 01083 01084 return true; 01085 } 01086 01087 # Give up. 01088 return true; 01089 } 01090 01095 protected function envCheckUploadsDirectory() { 01096 global $IP; 01097 01098 $dir = $IP . '/images/'; 01099 $url = $this->getVar( 'wgServer' ) . $this->getVar( 'wgScriptPath' ) . '/images/'; 01100 $safe = !$this->dirIsExecutable( $dir, $url ); 01101 01102 if ( !$safe ) { 01103 $this->showMessage( 'config-uploads-not-safe', $dir ); 01104 } 01105 01106 return true; 01107 } 01108 01114 protected function envCheckSuhosinMaxValueLength() { 01115 $maxValueLength = ini_get( 'suhosin.get.max_value_length' ); 01116 if ( $maxValueLength > 0 && $maxValueLength < 1024 ) { 01117 // Only warn if the value is below the sane 1024 01118 $this->showMessage( 'config-suhosin-max-value-length', $maxValueLength ); 01119 } 01120 01121 return true; 01122 } 01123 01129 protected function unicodeChar( $c ) { 01130 $c = hexdec( $c ); 01131 if ( $c <= 0x7F ) { 01132 return chr( $c ); 01133 } elseif ( $c <= 0x7FF ) { 01134 return chr( 0xC0 | $c >> 6 ) . chr( 0x80 | $c & 0x3F ); 01135 } elseif ( $c <= 0xFFFF ) { 01136 return chr( 0xE0 | $c >> 12 ) . chr( 0x80 | $c >> 6 & 0x3F ) 01137 . chr( 0x80 | $c & 0x3F ); 01138 } elseif ( $c <= 0x10FFFF ) { 01139 return chr( 0xF0 | $c >> 18 ) . chr( 0x80 | $c >> 12 & 0x3F ) 01140 . chr( 0x80 | $c >> 6 & 0x3F ) 01141 . chr( 0x80 | $c & 0x3F ); 01142 } else { 01143 return false; 01144 } 01145 } 01146 01150 protected function envCheckLibicu() { 01151 $utf8 = function_exists( 'utf8_normalize' ); 01152 $intl = function_exists( 'normalizer_normalize' ); 01153 01161 $not_normal_c = $this->unicodeChar( "FA6C" ); 01162 $normal_c = $this->unicodeChar( "242EE" ); 01163 01164 $useNormalizer = 'php'; 01165 $needsUpdate = false; 01166 01171 if ( $utf8 ) { 01172 $useNormalizer = 'utf8'; 01173 $utf8 = utf8_normalize( $not_normal_c, UtfNormal::UNORM_NFC ); 01174 if ( $utf8 !== $normal_c ) { 01175 $needsUpdate = true; 01176 } 01177 } 01178 if ( $intl ) { 01179 $useNormalizer = 'intl'; 01180 $intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C ); 01181 if ( $intl !== $normal_c ) { 01182 $needsUpdate = true; 01183 } 01184 } 01185 01186 // Uses messages 'config-unicode-using-php', 'config-unicode-using-utf8', 01187 // 'config-unicode-using-intl' 01188 if ( $useNormalizer === 'php' ) { 01189 $this->showMessage( 'config-unicode-pure-php-warning' ); 01190 } else { 01191 $this->showMessage( 'config-unicode-using-' . $useNormalizer ); 01192 if ( $needsUpdate ) { 01193 $this->showMessage( 'config-unicode-update-warning' ); 01194 } 01195 } 01196 } 01197 01201 protected function envCheckCtype() { 01202 if ( !function_exists( 'ctype_digit' ) ) { 01203 $this->showError( 'config-ctype' ); 01204 01205 return false; 01206 } 01207 01208 return true; 01209 } 01210 01214 protected function envCheckJSON() { 01215 if ( !function_exists( 'json_decode' ) ) { 01216 $this->showError( 'config-json' ); 01217 01218 return false; 01219 } 01220 01221 return true; 01222 } 01223 01231 protected static function getPossibleBinPaths() { 01232 return array_merge( 01233 array( '/usr/bin', '/usr/local/bin', '/opt/csw/bin', 01234 '/usr/gnu/bin', '/usr/sfw/bin', '/sw/bin', '/opt/local/bin' ), 01235 explode( PATH_SEPARATOR, getenv( 'PATH' ) ) 01236 ); 01237 } 01238 01256 public static function locateExecutable( $path, $names, $versionInfo = false ) { 01257 if ( !is_array( $names ) ) { 01258 $names = array( $names ); 01259 } 01260 01261 foreach ( $names as $name ) { 01262 $command = $path . DIRECTORY_SEPARATOR . $name; 01263 01264 wfSuppressWarnings(); 01265 $file_exists = file_exists( $command ); 01266 wfRestoreWarnings(); 01267 01268 if ( $file_exists ) { 01269 if ( !$versionInfo ) { 01270 return $command; 01271 } 01272 01273 $file = str_replace( '$1', wfEscapeShellArg( $command ), $versionInfo[0] ); 01274 if ( strstr( wfShellExec( $file ), $versionInfo[1] ) !== false ) { 01275 return $command; 01276 } 01277 } 01278 } 01279 01280 return false; 01281 } 01282 01295 public static function locateExecutableInDefaultPaths( $names, $versionInfo = false ) { 01296 foreach ( self::getPossibleBinPaths() as $path ) { 01297 $exe = self::locateExecutable( $path, $names, $versionInfo ); 01298 if ( $exe !== false ) { 01299 return $exe; 01300 } 01301 } 01302 01303 return false; 01304 } 01305 01314 public function dirIsExecutable( $dir, $url ) { 01315 $scriptTypes = array( 01316 'php' => array( 01317 "<?php echo 'ex' . 'ec';", 01318 "#!/var/env php5\n<?php echo 'ex' . 'ec';", 01319 ), 01320 ); 01321 01322 // it would be good to check other popular languages here, but it'll be slow. 01323 01324 wfSuppressWarnings(); 01325 01326 foreach ( $scriptTypes as $ext => $contents ) { 01327 foreach ( $contents as $source ) { 01328 $file = 'exectest.' . $ext; 01329 01330 if ( !file_put_contents( $dir . $file, $source ) ) { 01331 break; 01332 } 01333 01334 try { 01335 $text = Http::get( $url . $file, array( 'timeout' => 3 ) ); 01336 } catch ( MWException $e ) { 01337 // Http::get throws with allow_url_fopen = false and no curl extension. 01338 $text = null; 01339 } 01340 unlink( $dir . $file ); 01341 01342 if ( $text == 'exec' ) { 01343 wfRestoreWarnings(); 01344 01345 return $ext; 01346 } 01347 } 01348 } 01349 01350 wfRestoreWarnings(); 01351 01352 return false; 01353 } 01354 01361 public static function apacheModulePresent( $moduleName ) { 01362 if ( function_exists( 'apache_get_modules' ) && in_array( $moduleName, apache_get_modules() ) ) { 01363 return true; 01364 } 01365 // try it the hard way 01366 ob_start(); 01367 phpinfo( INFO_MODULES ); 01368 $info = ob_get_clean(); 01369 01370 return strpos( $info, $moduleName ) !== false; 01371 } 01372 01378 public function setParserLanguage( $lang ) { 01379 $this->parserOptions->setTargetLanguage( $lang ); 01380 $this->parserOptions->setUserLang( $lang ); 01381 } 01382 01388 protected function getDocUrl( $page ) { 01389 return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page ); 01390 } 01391 01398 public function findExtensions() { 01399 if ( $this->getVar( 'IP' ) === null ) { 01400 return array(); 01401 } 01402 01403 $extDir = $this->getVar( 'IP' ) . '/extensions'; 01404 if ( !is_readable( $extDir ) || !is_dir( $extDir ) ) { 01405 return array(); 01406 } 01407 01408 $dh = opendir( $extDir ); 01409 $exts = array(); 01410 while ( ( $file = readdir( $dh ) ) !== false ) { 01411 if ( !is_dir( "$extDir/$file" ) ) { 01412 continue; 01413 } 01414 if ( file_exists( "$extDir/$file/$file.php" ) ) { 01415 $exts[] = $file; 01416 } 01417 } 01418 closedir( $dh ); 01419 natcasesort( $exts ); 01420 01421 return $exts; 01422 } 01423 01429 protected function includeExtensions() { 01430 global $IP; 01431 $exts = $this->getVar( '_Extensions' ); 01432 $IP = $this->getVar( 'IP' ); 01433 01442 global $wgAutoloadClasses; 01443 $wgAutoloadClasses = array(); 01444 01445 require "$IP/includes/DefaultSettings.php"; 01446 01447 foreach ( $exts as $e ) { 01448 require_once "$IP/extensions/$e/$e.php"; 01449 } 01450 01451 $hooksWeWant = isset( $wgHooks['LoadExtensionSchemaUpdates'] ) ? 01452 $wgHooks['LoadExtensionSchemaUpdates'] : array(); 01453 01454 // Unset everyone else's hooks. Lord knows what someone might be doing 01455 // in ParserFirstCallInit (see bug 27171) 01456 $GLOBALS['wgHooks'] = array( 'LoadExtensionSchemaUpdates' => $hooksWeWant ); 01457 01458 return Status::newGood(); 01459 } 01460 01473 protected function getInstallSteps( DatabaseInstaller $installer ) { 01474 $coreInstallSteps = array( 01475 array( 'name' => 'database', 'callback' => array( $installer, 'setupDatabase' ) ), 01476 array( 'name' => 'tables', 'callback' => array( $installer, 'createTables' ) ), 01477 array( 'name' => 'interwiki', 'callback' => array( $installer, 'populateInterwikiTable' ) ), 01478 array( 'name' => 'stats', 'callback' => array( $this, 'populateSiteStats' ) ), 01479 array( 'name' => 'keys', 'callback' => array( $this, 'generateKeys' ) ), 01480 array( 'name' => 'sysop', 'callback' => array( $this, 'createSysop' ) ), 01481 array( 'name' => 'mainpage', 'callback' => array( $this, 'createMainpage' ) ), 01482 ); 01483 01484 // Build the array of install steps starting from the core install list, 01485 // then adding any callbacks that wanted to attach after a given step 01486 foreach ( $coreInstallSteps as $step ) { 01487 $this->installSteps[] = $step; 01488 if ( isset( $this->extraInstallSteps[$step['name']] ) ) { 01489 $this->installSteps = array_merge( 01490 $this->installSteps, 01491 $this->extraInstallSteps[$step['name']] 01492 ); 01493 } 01494 } 01495 01496 // Prepend any steps that want to be at the beginning 01497 if ( isset( $this->extraInstallSteps['BEGINNING'] ) ) { 01498 $this->installSteps = array_merge( 01499 $this->extraInstallSteps['BEGINNING'], 01500 $this->installSteps 01501 ); 01502 } 01503 01504 // Extensions should always go first, chance to tie into hooks and such 01505 if ( count( $this->getVar( '_Extensions' ) ) ) { 01506 array_unshift( $this->installSteps, 01507 array( 'name' => 'extensions', 'callback' => array( $this, 'includeExtensions' ) ) 01508 ); 01509 $this->installSteps[] = array( 01510 'name' => 'extension-tables', 01511 'callback' => array( $installer, 'createExtensionTables' ) 01512 ); 01513 } 01514 01515 return $this->installSteps; 01516 } 01517 01526 public function performInstallation( $startCB, $endCB ) { 01527 $installResults = array(); 01528 $installer = $this->getDBInstaller(); 01529 $installer->preInstall(); 01530 $steps = $this->getInstallSteps( $installer ); 01531 foreach ( $steps as $stepObj ) { 01532 $name = $stepObj['name']; 01533 call_user_func_array( $startCB, array( $name ) ); 01534 01535 // Perform the callback step 01536 $status = call_user_func( $stepObj['callback'], $installer ); 01537 01538 // Output and save the results 01539 call_user_func( $endCB, $name, $status ); 01540 $installResults[$name] = $status; 01541 01542 // If we've hit some sort of fatal, we need to bail. 01543 // Callback already had a chance to do output above. 01544 if ( !$status->isOk() ) { 01545 break; 01546 } 01547 } 01548 if ( $status->isOk() ) { 01549 $this->setVar( '_InstallDone', true ); 01550 } 01551 01552 return $installResults; 01553 } 01554 01560 public function generateKeys() { 01561 $keys = array( 'wgSecretKey' => 64 ); 01562 if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) { 01563 $keys['wgUpgradeKey'] = 16; 01564 } 01565 01566 return $this->doGenerateKeys( $keys ); 01567 } 01568 01576 protected function doGenerateKeys( $keys ) { 01577 $status = Status::newGood(); 01578 01579 $strong = true; 01580 foreach ( $keys as $name => $length ) { 01581 $secretKey = MWCryptRand::generateHex( $length, true ); 01582 if ( !MWCryptRand::wasStrong() ) { 01583 $strong = false; 01584 } 01585 01586 $this->setVar( $name, $secretKey ); 01587 } 01588 01589 if ( !$strong ) { 01590 $names = array_keys( $keys ); 01591 $names = preg_replace( '/^(.*)$/', '\$$1', $names ); 01592 global $wgLang; 01593 $status->warning( 'config-insecure-keys', $wgLang->listToText( $names ), count( $names ) ); 01594 } 01595 01596 return $status; 01597 } 01598 01604 protected function createSysop() { 01605 $name = $this->getVar( '_AdminName' ); 01606 $user = User::newFromName( $name ); 01607 01608 if ( !$user ) { 01609 // We should've validated this earlier anyway! 01610 return Status::newFatal( 'config-admin-error-user', $name ); 01611 } 01612 01613 if ( $user->idForName() == 0 ) { 01614 $user->addToDatabase(); 01615 01616 try { 01617 $user->setPassword( $this->getVar( '_AdminPassword' ) ); 01618 } catch ( PasswordError $pwe ) { 01619 return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() ); 01620 } 01621 01622 $user->addGroup( 'sysop' ); 01623 $user->addGroup( 'bureaucrat' ); 01624 if ( $this->getVar( '_AdminEmail' ) ) { 01625 $user->setEmail( $this->getVar( '_AdminEmail' ) ); 01626 } 01627 $user->saveSettings(); 01628 01629 // Update user count 01630 $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 ); 01631 $ssUpdate->doUpdate(); 01632 } 01633 $status = Status::newGood(); 01634 01635 if ( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) { 01636 $this->subscribeToMediaWikiAnnounce( $status ); 01637 } 01638 01639 return $status; 01640 } 01641 01645 private function subscribeToMediaWikiAnnounce( Status $s ) { 01646 $params = array( 01647 'email' => $this->getVar( '_AdminEmail' ), 01648 'language' => 'en', 01649 'digest' => 0 01650 ); 01651 01652 // Mailman doesn't support as many languages as we do, so check to make 01653 // sure their selected language is available 01654 $myLang = $this->getVar( '_UserLang' ); 01655 if ( in_array( $myLang, $this->mediaWikiAnnounceLanguages ) ) { 01656 $myLang = $myLang == 'pt-br' ? 'pt_BR' : $myLang; // rewrite to Mailman's pt_BR 01657 $params['language'] = $myLang; 01658 } 01659 01660 if ( MWHttpRequest::canMakeRequests() ) { 01661 $res = MWHttpRequest::factory( $this->mediaWikiAnnounceUrl, 01662 array( 'method' => 'POST', 'postData' => $params ) )->execute(); 01663 if ( !$res->isOK() ) { 01664 $s->warning( 'config-install-subscribe-fail', $res->getMessage() ); 01665 } 01666 } else { 01667 $s->warning( 'config-install-subscribe-notpossible' ); 01668 } 01669 } 01670 01677 protected function createMainpage( DatabaseInstaller $installer ) { 01678 $status = Status::newGood(); 01679 try { 01680 $page = WikiPage::factory( Title::newMainPage() ); 01681 $content = new WikitextContent( 01682 wfMessage( 'mainpagetext' )->inContentLanguage()->text() . "\n\n" . 01683 wfMessage( 'mainpagedocfooter' )->inContentLanguage()->text() 01684 ); 01685 01686 $page->doEditContent( $content, 01687 '', 01688 EDIT_NEW, 01689 false, 01690 User::newFromName( 'MediaWiki default' ) 01691 ); 01692 } catch ( MWException $e ) { 01693 //using raw, because $wgShowExceptionDetails can not be set yet 01694 $status->fatal( 'config-install-mainpage-failed', $e->getMessage() ); 01695 } 01696 01697 return $status; 01698 } 01699 01703 public static function overrideConfig() { 01704 define( 'MW_NO_SESSION', 1 ); 01705 01706 // Don't access the database 01707 $GLOBALS['wgUseDatabaseMessages'] = false; 01708 // Don't cache langconv tables 01709 $GLOBALS['wgLanguageConverterCacheType'] = CACHE_NONE; 01710 // Debug-friendly 01711 $GLOBALS['wgShowExceptionDetails'] = true; 01712 // Don't break forms 01713 $GLOBALS['wgExternalLinkTarget'] = '_blank'; 01714 01715 // Extended debugging 01716 $GLOBALS['wgShowSQLErrors'] = true; 01717 $GLOBALS['wgShowDBErrorBacktrace'] = true; 01718 01719 // Allow multiple ob_flush() calls 01720 $GLOBALS['wgDisableOutputCompression'] = true; 01721 01722 // Use a sensible cookie prefix (not my_wiki) 01723 $GLOBALS['wgCookiePrefix'] = 'mw_installer'; 01724 01725 // Some of the environment checks make shell requests, remove limits 01726 $GLOBALS['wgMaxShellMemory'] = 0; 01727 01728 // Don't bother embedding images into generated CSS, which is not cached 01729 $GLOBALS['wgResourceLoaderLESSFunctions']['embeddable'] = function ( $frame, $less ) { 01730 return $less->toBool( false ); 01731 }; 01732 } 01733 01741 public function addInstallStep( $callback, $findStep = 'BEGINNING' ) { 01742 $this->extraInstallSteps[$findStep][] = $callback; 01743 } 01744 01749 protected function disableTimeLimit() { 01750 wfSuppressWarnings(); 01751 set_time_limit( 0 ); 01752 wfRestoreWarnings(); 01753 } 01754 }