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