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