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