MediaWiki  REL1_19
Installer.php
Go to the documentation of this file.
00001 <?php
00024 abstract class Installer {
00025 
00026         // This is the absolute minimum PHP version we can support
00027         const MINIMUM_PHP_VERSION = '5.2.3';
00028 
00032         protected $settings;
00033 
00039         protected $dbInstallers = array();
00040 
00046         protected $minMemorySize = 50;
00047 
00053         protected $parserTitle;
00054 
00060         protected $parserOptions;
00061 
00071         protected static $dbTypes = array(
00072                 'mysql',
00073                 'postgres',
00074                 'oracle',
00075                 'sqlite',
00076                 'ibm_db2',
00077         );
00078 
00086         protected $envChecks = array(
00087                 'envCheckDB',
00088                 'envCheckRegisterGlobals',
00089                 'envCheckBrokenXML',
00090                 'envCheckPHP531',
00091                 'envCheckMagicQuotes',
00092                 'envCheckMagicSybase',
00093                 'envCheckMbstring',
00094                 'envCheckZE1',
00095                 'envCheckSafeMode',
00096                 'envCheckXML',
00097                 'envCheckPCRE',
00098                 'envCheckMemory',
00099                 'envCheckCache',
00100                 'envCheckModSecurity',
00101                 'envCheckDiff3',
00102                 'envCheckGraphics',
00103                 'envCheckServer',
00104                 'envCheckPath',
00105                 'envCheckExtension',
00106                 'envCheckShellLocale',
00107                 'envCheckUploadsDirectory',
00108                 'envCheckLibicu',
00109                 'envCheckSuhosinMaxValueLength',
00110                 'envCheckCtype',
00111         );
00112 
00120         protected $defaultVarNames = array(
00121                 'wgSitename',
00122                 'wgPasswordSender',
00123                 'wgLanguageCode',
00124                 'wgRightsIcon',
00125                 'wgRightsText',
00126                 'wgRightsUrl',
00127                 'wgMainCacheType',
00128                 'wgEnableEmail',
00129                 'wgEnableUserEmail',
00130                 'wgEnotifUserTalk',
00131                 'wgEnotifWatchlist',
00132                 'wgEmailAuthentication',
00133                 'wgDBtype',
00134                 'wgDiff3',
00135                 'wgImageMagickConvertCommand',
00136                 'IP',
00137                 'wgServer',
00138                 'wgScriptPath',
00139                 'wgScriptExtension',
00140                 'wgMetaNamespace',
00141                 'wgDeletedDirectory',
00142                 'wgEnableUploads',
00143                 'wgLogo',
00144                 'wgShellLocale',
00145                 'wgSecretKey',
00146                 'wgUseInstantCommons',
00147                 'wgUpgradeKey',
00148                 'wgDefaultSkin',
00149                 'wgResourceLoaderMaxQueryLength',
00150         );
00151 
00159         protected $internalDefaults = array(
00160                 '_UserLang' => 'en',
00161                 '_Environment' => false,
00162                 '_CompiledDBs' => array(),
00163                 '_SafeMode' => false,
00164                 '_RaiseMemory' => false,
00165                 '_UpgradeDone' => false,
00166                 '_InstallDone' => false,
00167                 '_Caches' => array(),
00168                 '_InstallPassword' => '',
00169                 '_SameAccount' => true,
00170                 '_CreateDBAccount' => false,
00171                 '_NamespaceType' => 'site-name',
00172                 '_AdminName' => '', // will be set later, when the user selects language
00173                 '_AdminPassword' => '',
00174                 '_AdminPassword2' => '',
00175                 '_AdminEmail' => '',
00176                 '_Subscribe' => false,
00177                 '_SkipOptional' => 'continue',
00178                 '_RightsProfile' => 'wiki',
00179                 '_LicenseCode' => 'none',
00180                 '_CCDone' => false,
00181                 '_Extensions' => array(),
00182                 '_MemCachedServers' => '',
00183                 '_UpgradeKeySupplied' => false,
00184                 '_ExistingDBSettings' => false,
00185         );
00186 
00192         private $installSteps = array();
00193 
00199         protected $extraInstallSteps = array();
00200 
00206         protected $objectCaches = array(
00207                 'xcache' => 'xcache_get',
00208                 'apc' => 'apc_fetch',
00209                 'wincache' => 'wincache_ucache_get'
00210         );
00211 
00217         public $rightsProfiles = array(
00218                 'wiki' => array(),
00219                 'no-anon' => array(
00220                         '*' => array( 'edit' => false )
00221                 ),
00222                 'fishbowl' => array(
00223                         '*' => array(
00224                                 'createaccount' => false,
00225                                 'edit' => false,
00226                         ),
00227                 ),
00228                 'private' => array(
00229                         '*' => array(
00230                                 'createaccount' => false,
00231                                 'edit' => false,
00232                                 'read' => false,
00233                         ),
00234                 ),
00235         );
00236 
00242         public $licenses = array(
00243                 'cc-by' => array(
00244                         'url' => 'http://creativecommons.org/licenses/by/3.0/',
00245                         'icon' => '{$wgStylePath}/common/images/cc-by.png',
00246                 ),
00247                 'cc-by-sa' => array(
00248                         'url' => 'http://creativecommons.org/licenses/by-sa/3.0/',
00249                         'icon' => '{$wgStylePath}/common/images/cc-by-sa.png',
00250                 ),
00251                 'cc-by-nc-sa' => array(
00252                         'url' => 'http://creativecommons.org/licenses/by-nc-sa/3.0/',
00253                         'icon' => '{$wgStylePath}/common/images/cc-by-nc-sa.png',
00254                 ),
00255                 'cc-0' => array(
00256                         'url' => 'https://creativecommons.org/publicdomain/zero/1.0/',
00257                         'icon' => '{$wgStylePath}/common/images/cc-0.png',
00258                 ),
00259                 'pd' => array(
00260                         'url' => '',
00261                         'icon' => '{$wgStylePath}/common/images/public-domain.png',
00262                 ),
00263                 'gfdl' => array(
00264                         'url' => 'http://www.gnu.org/copyleft/fdl.html',
00265                         'icon' => '{$wgStylePath}/common/images/gnu-fdl.png',
00266                 ),
00267                 'none' => array(
00268                         'url' => '',
00269                         'icon' => '',
00270                         'text' => ''
00271                 ),
00272                 'cc-choose' => array(
00273                         // Details will be filled in by the selector.
00274                         'url' => '',
00275                         'icon' => '',
00276                         'text' => '',
00277                 ),
00278         );
00279 
00283         protected $mediaWikiAnnounceUrl = 'https://lists.wikimedia.org/mailman/subscribe/mediawiki-announce';
00284 
00288         protected $mediaWikiAnnounceLanguages = array(
00289                 'ca', 'cs', 'da', 'de', 'en', 'es', 'et', 'eu', 'fi', 'fr', 'hr', 'hu',
00290                 'it', 'ja', 'ko', 'lt', 'nl', 'no', 'pl', 'pt', 'pt-br', 'ro', 'ru',
00291                 'sl', 'sr', 'sv', 'tr', 'uk'
00292         );
00293 
00301         public abstract function showMessage( $msg /*, ... */ );
00302 
00307         public abstract function showError( $msg /*, ... */ );
00308 
00313         public abstract function showStatusMessage( Status $status );
00314 
00318         public function __construct() {
00319                 global $wgExtensionMessagesFiles, $wgUser;
00320 
00321                 // Disable the i18n cache and LoadBalancer
00322                 Language::getLocalisationCache()->disableBackend();
00323                 LBFactory::disableBackend();
00324 
00325                 // Load the installer's i18n file.
00326                 $wgExtensionMessagesFiles['MediawikiInstaller'] =
00327                         dirname( __FILE__ ) . '/Installer.i18n.php';
00328 
00329                 // Having a user with id = 0 safeguards us from DB access via User::loadOptions().
00330                 $wgUser = User::newFromId( 0 );
00331 
00332                 $this->settings = $this->internalDefaults;
00333 
00334                 foreach ( $this->defaultVarNames as $var ) {
00335                         $this->settings[$var] = $GLOBALS[$var];
00336                 }
00337 
00338                 $compiledDBs = array();
00339                 foreach ( self::getDBTypes() as $type ) {
00340                         $installer = $this->getDBInstaller( $type );
00341 
00342                         if ( !$installer->isCompiled() ) {
00343                                 continue;
00344                         }
00345                         $compiledDBs[] = $type;
00346 
00347                         $defaults = $installer->getGlobalDefaults();
00348 
00349                         foreach ( $installer->getGlobalNames() as $var ) {
00350                                 if ( isset( $defaults[$var] ) ) {
00351                                         $this->settings[$var] = $defaults[$var];
00352                                 } else {
00353                                         $this->settings[$var] = $GLOBALS[$var];
00354                                 }
00355                         }
00356                 }
00357                 $this->setVar( '_CompiledDBs', $compiledDBs );
00358 
00359                 $this->parserTitle = Title::newFromText( 'Installer' );
00360                 $this->parserOptions = new ParserOptions; // language will  be wrong :(
00361                 $this->parserOptions->setEditSection( false );
00362         }
00363 
00369         public static function getDBTypes() {
00370                 return self::$dbTypes;
00371         }
00372 
00386         public function doEnvironmentChecks() {
00387                 $phpVersion = phpversion();
00388                 if( version_compare( $phpVersion, self::MINIMUM_PHP_VERSION, '>=' ) ) {
00389                         $this->showMessage( 'config-env-php', $phpVersion );
00390                         $good = true;
00391                 } else {
00392                         $this->showMessage( 'config-env-php-toolow', $phpVersion, self::MINIMUM_PHP_VERSION );
00393                         $good = false;
00394                 }
00395 
00396                 if( $good ) {
00397                         foreach ( $this->envChecks as $check ) {
00398                                 $status = $this->$check();
00399                                 if ( $status === false ) {
00400                                         $good = false;
00401                                 }
00402                         }
00403                 }
00404 
00405                 $this->setVar( '_Environment', $good );
00406 
00407                 return $good ? Status::newGood() : Status::newFatal( 'config-env-bad' );
00408         }
00409 
00416         public function setVar( $name, $value ) {
00417                 $this->settings[$name] = $value;
00418         }
00419 
00430         public function getVar( $name, $default = null ) {
00431                 if ( !isset( $this->settings[$name] ) ) {
00432                         return $default;
00433                 } else {
00434                         return $this->settings[$name];
00435                 }
00436         }
00437 
00445         public function getDBInstaller( $type = false ) {
00446                 if ( !$type ) {
00447                         $type = $this->getVar( 'wgDBtype' );
00448                 }
00449 
00450                 $type = strtolower( $type );
00451 
00452                 if ( !isset( $this->dbInstallers[$type] ) ) {
00453                         $class = ucfirst( $type ). 'Installer';
00454                         $this->dbInstallers[$type] = new $class( $this );
00455                 }
00456 
00457                 return $this->dbInstallers[$type];
00458         }
00459 
00466         public static function getExistingLocalSettings() {
00467                 global $IP;
00468 
00469                 wfSuppressWarnings();
00470                 $_lsExists = file_exists( "$IP/LocalSettings.php" );
00471                 wfRestoreWarnings();
00472 
00473                 if( !$_lsExists ) {
00474                         return false;
00475                 }
00476                 unset($_lsExists);
00477 
00478                 require( "$IP/includes/DefaultSettings.php" );
00479                 require( "$IP/LocalSettings.php" );
00480                 if ( file_exists( "$IP/AdminSettings.php" ) ) {
00481                         require( "$IP/AdminSettings.php" );
00482                 }
00483                 return get_defined_vars();
00484         }
00485 
00495         public function getFakePassword( $realPassword ) {
00496                 return str_repeat( '*', strlen( $realPassword ) );
00497         }
00498 
00506         public function setPassword( $name, $value ) {
00507                 if ( !preg_match( '/^\*+$/', $value ) ) {
00508                         $this->setVar( $name, $value );
00509                 }
00510         }
00511 
00523         public static function maybeGetWebserverPrimaryGroup() {
00524                 if ( !function_exists( 'posix_getegid' ) || !function_exists( 'posix_getpwuid' ) ) {
00525                         # I don't know this, this isn't UNIX.
00526                         return null;
00527                 }
00528 
00529                 # posix_getegid() *not* getmygid() because we want the group of the webserver,
00530                 # not whoever owns the current script.
00531                 $gid = posix_getegid();
00532                 $getpwuid = posix_getpwuid( $gid );
00533                 $group = $getpwuid['name'];
00534 
00535                 return $group;
00536         }
00537 
00554         public function parse( $text, $lineStart = false ) {
00555                 global $wgParser;
00556 
00557                 try {
00558                         $out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
00559                         $html = $out->getText();
00560                 } catch ( DBAccessError $e ) {
00561                         $html = '<!--DB access attempted during parse-->  ' . htmlspecialchars( $text );
00562 
00563                         if ( !empty( $this->debug ) ) {
00564                                 $html .= "<!--\n" . $e->getTraceAsString() . "\n-->";
00565                         }
00566                 }
00567 
00568                 return $html;
00569         }
00570 
00574         public function getParserOptions() {
00575                 return $this->parserOptions;
00576         }
00577 
00578         public function disableLinkPopups() {
00579                 $this->parserOptions->setExternalLinkTarget( false );
00580         }
00581 
00582         public function restoreLinkPopups() {
00583                 global $wgExternalLinkTarget;
00584                 $this->parserOptions->setExternalLinkTarget( $wgExternalLinkTarget );
00585         }
00586 
00595         public function populateSiteStats( DatabaseInstaller $installer ) {
00596                 $status = $installer->getConnection();
00597                 if ( !$status->isOK() ) {
00598                         return $status;
00599                 }
00600                 $status->value->insert( 'site_stats', array(
00601                         'ss_row_id' => 1,
00602                         'ss_total_views' => 0,
00603                         'ss_total_edits' => 0,
00604                         'ss_good_articles' => 0,
00605                         'ss_total_pages' => 0,
00606                         'ss_users' => 0,
00607                         'ss_images' => 0 ),
00608                         __METHOD__, 'IGNORE' );
00609                 return Status::newGood();
00610         }
00611 
00615         public function exportVars() {
00616                 foreach ( $this->settings as $name => $value ) {
00617                         if ( substr( $name, 0, 2 ) == 'wg' ) {
00618                                 $GLOBALS[$name] = $value;
00619                         }
00620                 }
00621         }
00622 
00627         protected function envCheckDB() {
00628                 global $wgLang;
00629 
00630                 $allNames = array();
00631 
00632                 foreach ( self::getDBTypes() as $name ) {
00633                         $allNames[] = wfMsg( "config-type-$name" );
00634                 }
00635 
00636                 // cache initially available databases to make sure that everything will be displayed correctly
00637                 // after a refresh on env checks page
00638                 $databases = $this->getVar( '_CompiledDBs-preFilter' );
00639                 if ( !$databases ) {
00640                         $databases = $this->getVar( '_CompiledDBs' );
00641                         $this->setVar( '_CompiledDBs-preFilter', $databases );
00642                 }
00643 
00644                 $databases = array_flip ( $databases );
00645                 foreach ( array_keys( $databases ) as $db ) {
00646                         $installer = $this->getDBInstaller( $db );
00647                         $status = $installer->checkPrerequisites();
00648                         if ( !$status->isGood() ) {
00649                                 $this->showStatusMessage( $status );
00650                         }
00651                         if ( !$status->isOK() ) {
00652                                 unset( $databases[$db] );
00653                         }
00654                 }
00655                 $databases = array_flip( $databases );
00656                 if ( !$databases ) {
00657                         $this->showError( 'config-no-db', $wgLang->commaList( $allNames ) );
00658                         // @todo FIXME: This only works for the web installer!
00659                         return false;
00660                 }
00661                 $this->setVar( '_CompiledDBs', $databases );
00662         }
00663 
00667         protected function envCheckRegisterGlobals() {
00668                 if( wfIniGetBool( 'register_globals' ) ) {
00669                         $this->showMessage( 'config-register-globals' );
00670                 }
00671         }
00672 
00676         protected function envCheckBrokenXML() {
00677                 $test = new PhpXmlBugTester();
00678                 if ( !$test->ok ) {
00679                         $this->showError( 'config-brokenlibxml' );
00680                         return false;
00681                 }
00682         }
00683 
00688         protected function envCheckPHP531() {
00689                 $test = new PhpRefCallBugTester;
00690                 $test->execute();
00691                 if ( !$test->ok ) {
00692                         $this->showError( 'config-using531', phpversion() );
00693                         return false;
00694                 }
00695         }
00696 
00700         protected function envCheckMagicQuotes() {
00701                 if( wfIniGetBool( "magic_quotes_runtime" ) ) {
00702                         $this->showError( 'config-magic-quotes-runtime' );
00703                         return false;
00704                 }
00705         }
00706 
00710         protected function envCheckMagicSybase() {
00711                 if ( wfIniGetBool( 'magic_quotes_sybase' ) ) {
00712                         $this->showError( 'config-magic-quotes-sybase' );
00713                         return false;
00714                 }
00715         }
00716 
00720         protected function envCheckMbstring() {
00721                 if ( wfIniGetBool( 'mbstring.func_overload' ) ) {
00722                         $this->showError( 'config-mbstring' );
00723                         return false;
00724                 }
00725         }
00726 
00730         protected function envCheckZE1() {
00731                 if ( wfIniGetBool( 'zend.ze1_compatibility_mode' ) ) {
00732                         $this->showError( 'config-ze1' );
00733                         return false;
00734                 }
00735         }
00736 
00740         protected function envCheckSafeMode() {
00741                 if ( wfIniGetBool( 'safe_mode' ) ) {
00742                         $this->setVar( '_SafeMode', true );
00743                         $this->showMessage( 'config-safe-mode' );
00744                 }
00745         }
00746 
00750         protected function envCheckXML() {
00751                 if ( !function_exists( "utf8_encode" ) ) {
00752                         $this->showError( 'config-xml-bad' );
00753                         return false;
00754                 }
00755         }
00756 
00765         protected function envCheckPCRE() {
00766                 if ( !function_exists( 'preg_match' ) ) {
00767                         $this->showError( 'config-pcre' );
00768                         return false;
00769                 }
00770                 wfSuppressWarnings();
00771                 $regexd = preg_replace( '/[\x{0430}-\x{04FF}]/iu', '', '-АБВГД-' );
00772                 // Need to check for \p support too, as PCRE can be compiled
00773                 // with utf8 support, but not unicode property support.
00774                 // check that \p{Zs} (space separators) matches
00775                 // U+3000 (Ideographic space)
00776                 $regexprop = preg_replace( '/\p{Zs}/u', '', "-\xE3\x80\x80-" );
00777                 wfRestoreWarnings();
00778                 if ( $regexd != '--' || $regexprop != '--' ) {
00779                         $this->showError( 'config-pcre-no-utf8' );
00780                         return false;
00781                 }
00782         }
00783 
00787         protected function envCheckMemory() {
00788                 $limit = ini_get( 'memory_limit' );
00789 
00790                 if ( !$limit || $limit == -1 ) {
00791                         return true;
00792                 }
00793 
00794                 $n = wfShorthandToInteger( $limit );
00795 
00796                 if( $n < $this->minMemorySize * 1024 * 1024 ) {
00797                         $newLimit = "{$this->minMemorySize}M";
00798 
00799                         if( ini_set( "memory_limit", $newLimit ) === false ) {
00800                                 $this->showMessage( 'config-memory-bad', $limit );
00801                         } else {
00802                                 $this->showMessage( 'config-memory-raised', $limit, $newLimit );
00803                                 $this->setVar( '_RaiseMemory', true );
00804                         }
00805                 } else {
00806                         return true;
00807                 }
00808         }
00809 
00813         protected function envCheckCache() {
00814                 $caches = array();
00815                 foreach ( $this->objectCaches as $name => $function ) {
00816                         if ( function_exists( $function ) ) {
00817                                 if ( $name == 'xcache' && !wfIniGetBool( 'xcache.var_size' ) ) {
00818                                         continue;
00819                                 }
00820                                 $caches[$name] = true;
00821                         }
00822                 }
00823 
00824                 if ( !$caches ) {
00825                         $this->showMessage( 'config-no-cache' );
00826                 }
00827 
00828                 $this->setVar( '_Caches', $caches );
00829         }
00830 
00834         protected function envCheckModSecurity() {
00835                 if ( self::apacheModulePresent( 'mod_security' ) ) {
00836                         $this->showMessage( 'config-mod-security' );
00837                 }
00838         }
00839 
00843         protected function envCheckDiff3() {
00844                 $names = array( "gdiff3", "diff3", "diff3.exe" );
00845                 $versionInfo = array( '$1 --version 2>&1', 'GNU diffutils' );
00846 
00847                 $diff3 = self::locateExecutableInDefaultPaths( $names, $versionInfo );
00848 
00849                 if ( $diff3 ) {
00850                         $this->setVar( 'wgDiff3', $diff3 );
00851                 } else {
00852                         $this->setVar( 'wgDiff3', false );
00853                         $this->showMessage( 'config-diff3-bad' );
00854                 }
00855         }
00856 
00860         protected function envCheckGraphics() {
00861                 $names = array( wfIsWindows() ? 'convert.exe' : 'convert' );
00862                 $convert = self::locateExecutableInDefaultPaths( $names, array( '$1 -version', 'ImageMagick' ) );
00863 
00864                 $this->setVar( 'wgImageMagickConvertCommand', '' );
00865                 if ( $convert ) {
00866                         $this->setVar( 'wgImageMagickConvertCommand', $convert );
00867                         $this->showMessage( 'config-imagemagick', $convert );
00868                         return true;
00869                 } elseif ( function_exists( 'imagejpeg' ) ) {
00870                         $this->showMessage( 'config-gd' );
00871                         return true;
00872                 } else {
00873                         $this->showMessage( 'config-no-scaling' );
00874                 }
00875         }
00876 
00880         protected function envCheckServer() {
00881                 $server = $this->envGetDefaultServer();
00882                 $this->showMessage( 'config-using-server', $server );
00883                 $this->setVar( 'wgServer', $server );
00884         }
00885 
00890         protected abstract function envGetDefaultServer();
00891 
00896         protected function envCheckPath() {
00897                 global $IP;
00898                 $IP = dirname( dirname( dirname( __FILE__ ) ) );
00899                 $this->setVar( 'IP', $IP );
00900 
00901                 $this->showMessage( 'config-using-uri', $this->getVar( 'wgServer' ), $this->getVar( 'wgScriptPath' ) );
00902                 return true;
00903         }
00904 
00908         protected function envCheckExtension() {
00909                 // @todo FIXME: Detect this properly
00910                 if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) {
00911                         $ext = 'php5';
00912                 } else {
00913                         $ext = 'php';
00914                 }
00915                 $this->setVar( 'wgScriptExtension', ".$ext" );
00916         }
00917 
00922         protected function envCheckShellLocale() {
00923                 $os = php_uname( 's' );
00924                 $supported = array( 'Linux', 'SunOS', 'HP-UX', 'Darwin' ); # Tested these
00925 
00926                 if ( !in_array( $os, $supported ) ) {
00927                         return true;
00928                 }
00929 
00930                 # Get a list of available locales.
00931                 $ret = false;
00932                 $lines = wfShellExec( '/usr/bin/locale -a', $ret );
00933 
00934                 if ( $ret ) {
00935                         return true;
00936                 }
00937 
00938                 $lines = wfArrayMap( 'trim', explode( "\n", $lines ) );
00939                 $candidatesByLocale = array();
00940                 $candidatesByLang = array();
00941 
00942                 foreach ( $lines as $line ) {
00943                         if ( $line === '' ) {
00944                                 continue;
00945                         }
00946 
00947                         if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) {
00948                                 continue;
00949                         }
00950 
00951                         list( $all, $lang, $territory, $charset, $modifier ) = $m;
00952 
00953                         $candidatesByLocale[$m[0]] = $m;
00954                         $candidatesByLang[$lang][] = $m;
00955                 }
00956 
00957                 # Try the current value of LANG.
00958                 if ( isset( $candidatesByLocale[ getenv( 'LANG' ) ] ) ) {
00959                         $this->setVar( 'wgShellLocale', getenv( 'LANG' ) );
00960                         return true;
00961                 }
00962 
00963                 # Try the most common ones.
00964                 $commonLocales = array( 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' );
00965                 foreach ( $commonLocales as $commonLocale ) {
00966                         if ( isset( $candidatesByLocale[$commonLocale] ) ) {
00967                                 $this->setVar( 'wgShellLocale', $commonLocale );
00968                                 return true;
00969                         }
00970                 }
00971 
00972                 # Is there an available locale in the Wiki's language?
00973                 $wikiLang = $this->getVar( 'wgLanguageCode' );
00974 
00975                 if ( isset( $candidatesByLang[$wikiLang] ) ) {
00976                         $m = reset( $candidatesByLang[$wikiLang] );
00977                         $this->setVar( 'wgShellLocale', $m[0] );
00978                         return true;
00979                 }
00980 
00981                 # Are there any at all?
00982                 if ( count( $candidatesByLocale ) ) {
00983                         $m = reset( $candidatesByLocale );
00984                         $this->setVar( 'wgShellLocale', $m[0] );
00985                         return true;
00986                 }
00987 
00988                 # Give up.
00989                 return true;
00990         }
00991 
00995         protected function envCheckUploadsDirectory() {
00996                 global $IP;
00997 
00998                 $dir = $IP . '/images/';
00999                 $url = $this->getVar( 'wgServer' ) . $this->getVar( 'wgScriptPath' ) . '/images/';
01000                 $safe = !$this->dirIsExecutable( $dir, $url );
01001 
01002                 if ( $safe ) {
01003                         return true;
01004                 } else {
01005                         $this->showMessage( 'config-uploads-not-safe', $dir );
01006                 }
01007         }
01008 
01014         protected function envCheckSuhosinMaxValueLength() {
01015                 $maxValueLength = ini_get( 'suhosin.get.max_value_length' );
01016                 if ( $maxValueLength > 0 ) {
01017                         if( $maxValueLength < 1024 ) {
01018                                 # Only warn if the value is below the sane 1024
01019                                 $this->showMessage( 'config-suhosin-max-value-length', $maxValueLength );
01020                         }
01021                 } else {
01022                         $maxValueLength = -1;
01023                 }
01024                 $this->setVar( 'wgResourceLoaderMaxQueryLength', $maxValueLength );
01025         }
01026 
01032         protected function unicodeChar( $c ) {
01033                 $c = hexdec($c);
01034                 if ($c <= 0x7F) {
01035                         return chr($c);
01036                 } elseif ($c <= 0x7FF) {
01037                         return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F);
01038                 } elseif ($c <= 0xFFFF) {
01039                         return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F)
01040                                 . chr(0x80 | $c & 0x3F);
01041                 } elseif ($c <= 0x10FFFF) {
01042                         return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F)
01043                                 . chr(0x80 | $c >> 6 & 0x3F)
01044                                 . chr(0x80 | $c & 0x3F);
01045                 } else {
01046                         return false;
01047                 }
01048         }
01049 
01050 
01054         protected function envCheckLibicu() {
01055                 $utf8 = function_exists( 'utf8_normalize' );
01056                 $intl = function_exists( 'normalizer_normalize' );
01057 
01065                 $not_normal_c = $this->unicodeChar("FA6C");
01066                 $normal_c = $this->unicodeChar("242EE");
01067 
01068                 $useNormalizer = 'php';
01069                 $needsUpdate = false;
01070 
01075                 if( $utf8 ) {
01076                         $useNormalizer = 'utf8';
01077                         $utf8 = utf8_normalize( $not_normal_c, UtfNormal::UNORM_NFC );
01078                         if ( $utf8 !== $normal_c ) $needsUpdate = true;
01079                 }
01080                 if( $intl ) {
01081                         $useNormalizer = 'intl';
01082                         $intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C );
01083                         if ( $intl !== $normal_c ) $needsUpdate = true;
01084                 }
01085 
01086                 // Uses messages 'config-unicode-using-php', 'config-unicode-using-utf8', 'config-unicode-using-intl'
01087                 if( $useNormalizer === 'php' ) {
01088                         $this->showMessage( 'config-unicode-pure-php-warning' );
01089                 } else {
01090                         $this->showMessage( 'config-unicode-using-' . $useNormalizer );
01091                         if( $needsUpdate ) {
01092                                 $this->showMessage( 'config-unicode-update-warning' );
01093                         }
01094                 }
01095         }
01096 
01097         protected function envCheckCtype() {
01098                 if ( !function_exists( 'ctype_digit' ) ) {
01099                         $this->showError( 'config-ctype' );
01100                         return false;
01101                 }
01102         }
01103 
01111         protected static function getPossibleBinPaths() {
01112                 return array_merge(
01113                         array( '/usr/bin', '/usr/local/bin', '/opt/csw/bin',
01114                                 '/usr/gnu/bin', '/usr/sfw/bin', '/sw/bin', '/opt/local/bin' ),
01115                         explode( PATH_SEPARATOR, getenv( 'PATH' ) )
01116                 );
01117         }
01118 
01135         public static function locateExecutable( $path, $names, $versionInfo = false ) {
01136                 if ( !is_array( $names ) ) {
01137                         $names = array( $names );
01138                 }
01139 
01140                 foreach ( $names as $name ) {
01141                         $command = $path . DIRECTORY_SEPARATOR . $name;
01142 
01143                         wfSuppressWarnings();
01144                         $file_exists = file_exists( $command );
01145                         wfRestoreWarnings();
01146 
01147                         if ( $file_exists ) {
01148                                 if ( !$versionInfo ) {
01149                                         return $command;
01150                                 }
01151 
01152                                 $file = str_replace( '$1', wfEscapeShellArg( $command ), $versionInfo[0] );
01153                                 if ( strstr( wfShellExec( $file ), $versionInfo[1] ) !== false ) {
01154                                         return $command;
01155                                 }
01156                         }
01157                 }
01158                 return false;
01159         }
01160 
01168         public static function locateExecutableInDefaultPaths( $names, $versionInfo = false ) {
01169                 foreach( self::getPossibleBinPaths() as $path ) {
01170                         $exe = self::locateExecutable( $path, $names, $versionInfo );
01171                         if( $exe !== false ) {
01172                                 return $exe;
01173                         }
01174                 }
01175                 return false;
01176         }
01177 
01183         public function dirIsExecutable( $dir, $url ) {
01184                 $scriptTypes = array(
01185                         'php' => array(
01186                                 "<?php echo 'ex' . 'ec';",
01187                                 "#!/var/env php5\n<?php echo 'ex' . 'ec';",
01188                         ),
01189                 );
01190 
01191                 // it would be good to check other popular languages here, but it'll be slow.
01192 
01193                 wfSuppressWarnings();
01194 
01195                 foreach ( $scriptTypes as $ext => $contents ) {
01196                         foreach ( $contents as $source ) {
01197                                 $file = 'exectest.' . $ext;
01198 
01199                                 if ( !file_put_contents( $dir . $file, $source ) ) {
01200                                         break;
01201                                 }
01202 
01203                                 try {
01204                                         $text = Http::get( $url . $file, array( 'timeout' => 3 ) );
01205                                 }
01206                                 catch( MWException $e ) {
01207                                         // Http::get throws with allow_url_fopen = false and no curl extension.
01208                                         $text = null;
01209                                 }
01210                                 unlink( $dir . $file );
01211 
01212                                 if ( $text == 'exec' ) {
01213                                         wfRestoreWarnings();
01214                                         return $ext;
01215                                 }
01216                         }
01217                 }
01218 
01219                 wfRestoreWarnings();
01220 
01221                 return false;
01222         }
01223 
01230         public static function apacheModulePresent( $moduleName ) {
01231                 if ( function_exists( 'apache_get_modules' ) && in_array( $moduleName, apache_get_modules() ) ) {
01232                         return true;
01233                 }
01234                 // try it the hard way
01235                 ob_start();
01236                 phpinfo( INFO_MODULES );
01237                 $info = ob_get_clean();
01238                 return strpos( $info, $moduleName ) !== false;
01239         }
01240 
01246         public function setParserLanguage( $lang ) {
01247                 $this->parserOptions->setTargetLanguage( $lang );
01248                 $this->parserOptions->setUserLang( $lang );
01249         }
01250 
01256         protected function getDocUrl( $page ) {
01257                 return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page );
01258         }
01259 
01266         public function findExtensions() {
01267                 if( $this->getVar( 'IP' ) === null ) {
01268                         return false;
01269                 }
01270 
01271                 $exts = array();
01272                 $extDir = $this->getVar( 'IP' ) . '/extensions';
01273                 $dh = opendir( $extDir );
01274 
01275                 while ( ( $file = readdir( $dh ) ) !== false ) {
01276                         if( !is_dir( "$extDir/$file" ) ) {
01277                                 continue;
01278                         }
01279                         if( file_exists( "$extDir/$file/$file.php" ) ) {
01280                                 $exts[] = $file;
01281                         }
01282                 }
01283                 natcasesort( $exts );
01284 
01285                 return $exts;
01286         }
01287 
01293         protected function includeExtensions() {
01294                 global $IP;
01295                 $exts = $this->getVar( '_Extensions' );
01296                 $IP = $this->getVar( 'IP' );
01297 
01306                 global $wgAutoloadClasses;
01307                 $wgAutoloadClasses = array();
01308 
01309                 require( "$IP/includes/DefaultSettings.php" );
01310 
01311                 foreach( $exts as $e ) {
01312                         require_once( "$IP/extensions/$e/$e.php" );
01313                 }
01314 
01315                 $hooksWeWant = isset( $wgHooks['LoadExtensionSchemaUpdates'] ) ?
01316                         $wgHooks['LoadExtensionSchemaUpdates'] : array();
01317 
01318                 // Unset everyone else's hooks. Lord knows what someone might be doing
01319                 // in ParserFirstCallInit (see bug 27171)
01320                 $GLOBALS['wgHooks'] = array( 'LoadExtensionSchemaUpdates' => $hooksWeWant );
01321 
01322                 return Status::newGood();
01323         }
01324 
01337         protected function getInstallSteps( DatabaseInstaller $installer ) {
01338                 $coreInstallSteps = array(
01339                         array( 'name' => 'database',   'callback' => array( $installer, 'setupDatabase' ) ),
01340                         array( 'name' => 'tables',     'callback' => array( $installer, 'createTables' ) ),
01341                         array( 'name' => 'interwiki',  'callback' => array( $installer, 'populateInterwikiTable' ) ),
01342                         array( 'name' => 'stats',      'callback' => array( $this, 'populateSiteStats' ) ),
01343                         array( 'name' => 'keys',       'callback' => array( $this, 'generateKeys' ) ),
01344                         array( 'name' => 'sysop',      'callback' => array( $this, 'createSysop' ) ),
01345                         array( 'name' => 'mainpage',   'callback' => array( $this, 'createMainpage' ) ),
01346                 );
01347 
01348                 // Build the array of install steps starting from the core install list,
01349                 // then adding any callbacks that wanted to attach after a given step
01350                 foreach( $coreInstallSteps as $step ) {
01351                         $this->installSteps[] = $step;
01352                         if( isset( $this->extraInstallSteps[ $step['name'] ] ) ) {
01353                                 $this->installSteps = array_merge(
01354                                         $this->installSteps,
01355                                         $this->extraInstallSteps[ $step['name'] ]
01356                                 );
01357                         }
01358                 }
01359 
01360                 // Prepend any steps that want to be at the beginning
01361                 if( isset( $this->extraInstallSteps['BEGINNING'] ) ) {
01362                         $this->installSteps = array_merge(
01363                                 $this->extraInstallSteps['BEGINNING'],
01364                                 $this->installSteps
01365                         );
01366                 }
01367 
01368                 // Extensions should always go first, chance to tie into hooks and such
01369                 if( count( $this->getVar( '_Extensions' ) ) ) {
01370                         array_unshift( $this->installSteps,
01371                                 array( 'name' => 'extensions', 'callback' => array( $this, 'includeExtensions' ) )
01372                         );
01373                         $this->installSteps[] = array(
01374                                 'name' => 'extension-tables',
01375                                 'callback' => array( $installer, 'createExtensionTables' )
01376                         );
01377                 }
01378                 return $this->installSteps;
01379         }
01380 
01389         public function performInstallation( $startCB, $endCB ) {
01390                 $installResults = array();
01391                 $installer = $this->getDBInstaller();
01392                 $installer->preInstall();
01393                 $steps = $this->getInstallSteps( $installer );
01394                 foreach( $steps as $stepObj ) {
01395                         $name = $stepObj['name'];
01396                         call_user_func_array( $startCB, array( $name ) );
01397 
01398                         // Perform the callback step
01399                         $status = call_user_func( $stepObj['callback'], $installer );
01400 
01401                         // Output and save the results
01402                         call_user_func( $endCB, $name, $status );
01403                         $installResults[$name] = $status;
01404 
01405                         // If we've hit some sort of fatal, we need to bail.
01406                         // Callback already had a chance to do output above.
01407                         if( !$status->isOk() ) {
01408                                 break;
01409                         }
01410                 }
01411                 if( $status->isOk() ) {
01412                         $this->setVar( '_InstallDone', true );
01413                 }
01414                 return $installResults;
01415         }
01416 
01422         public function generateKeys() {
01423                 $keys = array( 'wgSecretKey' => 64 );
01424                 if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) {
01425                         $keys['wgUpgradeKey'] = 16;
01426                 }
01427                 return $this->doGenerateKeys( $keys );
01428         }
01429 
01437         protected function doGenerateKeys( $keys ) {
01438                 $status = Status::newGood();
01439 
01440                 $strong = true;
01441                 foreach ( $keys as $name => $length ) {
01442                         $secretKey = MWCryptRand::generateHex( $length, true );
01443                         if ( !MWCryptRand::wasStrong() ) {
01444                                 $strong = false;
01445                         }
01446 
01447                         $this->setVar( $name, $secretKey );
01448                 }
01449 
01450                 if ( !$strong ) {
01451                         $names = array_keys( $keys );
01452                         $names = preg_replace( '/^(.*)$/', '\$$1', $names );
01453                         global $wgLang;
01454                         $status->warning( 'config-insecure-keys', $wgLang->listToText( $names ), count( $names ) );
01455                 }
01456 
01457                 return $status;
01458         }
01459 
01465         protected function createSysop() {
01466                 $name = $this->getVar( '_AdminName' );
01467                 $user = User::newFromName( $name );
01468 
01469                 if ( !$user ) {
01470                         // We should've validated this earlier anyway!
01471                         return Status::newFatal( 'config-admin-error-user', $name );
01472                 }
01473 
01474                 if ( $user->idForName() == 0 ) {
01475                         $user->addToDatabase();
01476 
01477                         try {
01478                                 $user->setPassword( $this->getVar( '_AdminPassword' ) );
01479                         } catch( PasswordError $pwe ) {
01480                                 return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() );
01481                         }
01482 
01483                         $user->addGroup( 'sysop' );
01484                         $user->addGroup( 'bureaucrat' );
01485                         if( $this->getVar( '_AdminEmail' ) ) {
01486                                 $user->setEmail( $this->getVar( '_AdminEmail' ) );
01487                         }
01488                         $user->saveSettings();
01489 
01490                         // Update user count
01491                         $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 );
01492                         $ssUpdate->doUpdate();
01493                 }
01494                 $status = Status::newGood();
01495 
01496                 if( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) {
01497                         $this->subscribeToMediaWikiAnnounce( $status );
01498                 }
01499 
01500                 return $status;
01501         }
01502 
01506         private function subscribeToMediaWikiAnnounce( Status $s ) {
01507                 $params = array(
01508                         'email'    => $this->getVar( '_AdminEmail' ),
01509                         'language' => 'en',
01510                         'digest'   => 0
01511                 );
01512 
01513                 // Mailman doesn't support as many languages as we do, so check to make
01514                 // sure their selected language is available
01515                 $myLang = $this->getVar( '_UserLang' );
01516                 if( in_array( $myLang, $this->mediaWikiAnnounceLanguages ) ) {
01517                         $myLang = $myLang == 'pt-br' ? 'pt_BR' : $myLang; // rewrite to Mailman's pt_BR
01518                         $params['language'] = $myLang;
01519                 }
01520 
01521                 if( MWHttpRequest::canMakeRequests() ) {
01522                         $res = MWHttpRequest::factory( $this->mediaWikiAnnounceUrl,
01523                                 array( 'method' => 'POST', 'postData' => $params ) )->execute();
01524                         if( !$res->isOK() ) {
01525                                 $s->warning( 'config-install-subscribe-fail', $res->getMessage() );
01526                         }
01527                 } else {
01528                         $s->warning( 'config-install-subscribe-notpossible' );
01529                 }
01530         }
01531 
01538         protected function createMainpage( DatabaseInstaller $installer ) {
01539                 $status = Status::newGood();
01540                 try {
01541                         $page = WikiPage::factory( Title::newMainPage() );
01542                         $page->doEdit( wfMsgForContent( 'mainpagetext' ) . "\n\n" .
01543                                                         wfMsgForContent( 'mainpagedocfooter' ),
01544                                                         '',
01545                                                         EDIT_NEW,
01546                                                         false,
01547                                                         User::newFromName( 'MediaWiki default' ) );
01548                 } catch (MWException $e) {
01549                         //using raw, because $wgShowExceptionDetails can not be set yet
01550                         $status->fatal( 'config-install-mainpage-failed', $e->getMessage() );
01551                 }
01552 
01553                 return $status;
01554         }
01555 
01559         public static function overrideConfig() {
01560                 define( 'MW_NO_SESSION', 1 );
01561 
01562                 // Don't access the database
01563                 $GLOBALS['wgUseDatabaseMessages'] = false;
01564                 // Debug-friendly
01565                 $GLOBALS['wgShowExceptionDetails'] = true;
01566                 // Don't break forms
01567                 $GLOBALS['wgExternalLinkTarget'] = '_blank';
01568 
01569                 // Extended debugging
01570                 $GLOBALS['wgShowSQLErrors'] = true;
01571                 $GLOBALS['wgShowDBErrorBacktrace'] = true;
01572 
01573                 // Allow multiple ob_flush() calls
01574                 $GLOBALS['wgDisableOutputCompression'] = true;
01575 
01576                 // Use a sensible cookie prefix (not my_wiki)
01577                 $GLOBALS['wgCookiePrefix'] = 'mw_installer';
01578 
01579                 // Some of the environment checks make shell requests, remove limits
01580                 $GLOBALS['wgMaxShellMemory'] = 0;
01581         }
01582 
01590         public function addInstallStep( $callback, $findStep = 'BEGINNING' ) {
01591                 $this->extraInstallSteps[$findStep][] = $callback;
01592         }
01593 
01598         protected function disableTimeLimit() {
01599                 wfSuppressWarnings();
01600                 set_time_limit( 0 );
01601                 wfRestoreWarnings();
01602         }
01603 }