MediaWiki  REL1_21
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 
00047         protected $settings;
00048 
00049 
00055         protected $compiledDBs;
00056 
00062         protected $dbInstallers = array();
00063 
00069         protected $minMemorySize = 50;
00070 
00076         protected $parserTitle;
00077 
00083         protected $parserOptions;
00084 
00094         protected static $dbTypes = array(
00095                 'mysql',
00096                 'postgres',
00097                 'oracle',
00098                 'sqlite',
00099         );
00100 
00108         protected $envChecks = array(
00109                 'envCheckDB',
00110                 'envCheckRegisterGlobals',
00111                 'envCheckBrokenXML',
00112                 'envCheckPHP531',
00113                 'envCheckMagicQuotes',
00114                 'envCheckMagicSybase',
00115                 'envCheckMbstring',
00116                 'envCheckZE1',
00117                 'envCheckSafeMode',
00118                 'envCheckXML',
00119                 'envCheckPCRE',
00120                 'envCheckMemory',
00121                 'envCheckCache',
00122                 'envCheckModSecurity',
00123                 'envCheckDiff3',
00124                 'envCheckGraphics',
00125                 'envCheckServer',
00126                 'envCheckPath',
00127                 'envCheckExtension',
00128                 'envCheckShellLocale',
00129                 'envCheckUploadsDirectory',
00130                 'envCheckLibicu',
00131                 'envCheckSuhosinMaxValueLength',
00132                 'envCheckCtype',
00133         );
00134 
00142         protected $defaultVarNames = array(
00143                 'wgSitename',
00144                 'wgPasswordSender',
00145                 'wgLanguageCode',
00146                 'wgRightsIcon',
00147                 'wgRightsText',
00148                 'wgRightsUrl',
00149                 'wgMainCacheType',
00150                 'wgEnableEmail',
00151                 'wgEnableUserEmail',
00152                 'wgEnotifUserTalk',
00153                 'wgEnotifWatchlist',
00154                 'wgEmailAuthentication',
00155                 'wgDBtype',
00156                 'wgDiff3',
00157                 'wgImageMagickConvertCommand',
00158                 'IP',
00159                 'wgServer',
00160                 'wgScriptPath',
00161                 'wgScriptExtension',
00162                 'wgMetaNamespace',
00163                 'wgDeletedDirectory',
00164                 'wgEnableUploads',
00165                 'wgLogo',
00166                 'wgShellLocale',
00167                 'wgSecretKey',
00168                 'wgUseInstantCommons',
00169                 'wgUpgradeKey',
00170                 'wgDefaultSkin',
00171                 'wgResourceLoaderMaxQueryLength',
00172         );
00173 
00181         protected $internalDefaults = array(
00182                 '_UserLang' => 'en',
00183                 '_Environment' => false,
00184                 '_SafeMode' => false,
00185                 '_RaiseMemory' => false,
00186                 '_UpgradeDone' => false,
00187                 '_InstallDone' => false,
00188                 '_Caches' => array(),
00189                 '_InstallPassword' => '',
00190                 '_SameAccount' => true,
00191                 '_CreateDBAccount' => false,
00192                 '_NamespaceType' => 'site-name',
00193                 '_AdminName' => '', // will be set later, when the user selects language
00194                 '_AdminPassword' => '',
00195                 '_AdminPassword2' => '',
00196                 '_AdminEmail' => '',
00197                 '_Subscribe' => false,
00198                 '_SkipOptional' => 'continue',
00199                 '_RightsProfile' => 'wiki',
00200                 '_LicenseCode' => 'none',
00201                 '_CCDone' => false,
00202                 '_Extensions' => array(),
00203                 '_MemCachedServers' => '',
00204                 '_UpgradeKeySupplied' => false,
00205                 '_ExistingDBSettings' => false,
00206         );
00207 
00213         private $installSteps = array();
00214 
00220         protected $extraInstallSteps = array();
00221 
00227         protected $objectCaches = array(
00228                 'xcache' => 'xcache_get',
00229                 'apc' => 'apc_fetch',
00230                 'wincache' => 'wincache_ucache_get'
00231         );
00232 
00238         public $rightsProfiles = array(
00239                 'wiki' => array(),
00240                 'no-anon' => array(
00241                         '*' => array( 'edit' => false )
00242                 ),
00243                 'fishbowl' => array(
00244                         '*' => array(
00245                                 'createaccount' => false,
00246                                 'edit' => false,
00247                         ),
00248                 ),
00249                 'private' => array(
00250                         '*' => array(
00251                                 'createaccount' => false,
00252                                 'edit' => false,
00253                                 'read' => false,
00254                         ),
00255                 ),
00256         );
00257 
00263         public $licenses = array(
00264                 'cc-by' => array(
00265                         'url' => 'http://creativecommons.org/licenses/by/3.0/',
00266                         'icon' => '{$wgStylePath}/common/images/cc-by.png',
00267                 ),
00268                 'cc-by-sa' => array(
00269                         'url' => 'http://creativecommons.org/licenses/by-sa/3.0/',
00270                         'icon' => '{$wgStylePath}/common/images/cc-by-sa.png',
00271                 ),
00272                 'cc-by-nc-sa' => array(
00273                         'url' => 'http://creativecommons.org/licenses/by-nc-sa/3.0/',
00274                         'icon' => '{$wgStylePath}/common/images/cc-by-nc-sa.png',
00275                 ),
00276                 'cc-0' => array(
00277                         'url' => 'https://creativecommons.org/publicdomain/zero/1.0/',
00278                         'icon' => '{$wgStylePath}/common/images/cc-0.png',
00279                 ),
00280                 'pd' => array(
00281                         'url' => '',
00282                         'icon' => '{$wgStylePath}/common/images/public-domain.png',
00283                 ),
00284                 'gfdl' => array(
00285                         'url' => 'http://www.gnu.org/copyleft/fdl.html',
00286                         'icon' => '{$wgStylePath}/common/images/gnu-fdl.png',
00287                 ),
00288                 'none' => array(
00289                         'url' => '',
00290                         'icon' => '',
00291                         'text' => ''
00292                 ),
00293                 'cc-choose' => array(
00294                         // Details will be filled in by the selector.
00295                         'url' => '',
00296                         'icon' => '',
00297                         'text' => '',
00298                 ),
00299         );
00300 
00304         protected $mediaWikiAnnounceUrl = 'https://lists.wikimedia.org/mailman/subscribe/mediawiki-announce';
00305 
00309         protected $mediaWikiAnnounceLanguages = array(
00310                 'ca', 'cs', 'da', 'de', 'en', 'es', 'et', 'eu', 'fi', 'fr', 'hr', 'hu',
00311                 'it', 'ja', 'ko', 'lt', 'nl', 'no', 'pl', 'pt', 'pt-br', 'ro', 'ru',
00312                 'sl', 'sr', 'sv', 'tr', 'uk'
00313         );
00314 
00322         abstract public function showMessage( $msg /*, ... */ );
00323 
00328         abstract public function showError( $msg /*, ... */ );
00329 
00334         abstract public function showStatusMessage( Status $status );
00335 
00339         public function __construct() {
00340                 global $wgExtensionMessagesFiles, $wgUser;
00341 
00342                 // Disable the i18n cache and LoadBalancer
00343                 Language::getLocalisationCache()->disableBackend();
00344                 LBFactory::disableBackend();
00345 
00346                 // Load the installer's i18n file.
00347                 $wgExtensionMessagesFiles['MediawikiInstaller'] =
00348                         __DIR__ . '/Installer.i18n.php';
00349 
00350                 // Having a user with id = 0 safeguards us from DB access via User::loadOptions().
00351                 $wgUser = User::newFromId( 0 );
00352 
00353                 $this->settings = $this->internalDefaults;
00354 
00355                 foreach ( $this->defaultVarNames as $var ) {
00356                         $this->settings[$var] = $GLOBALS[$var];
00357                 }
00358 
00359                 $compiledDBs = array();
00360                 foreach ( self::getDBTypes() as $type ) {
00361                         $installer = $this->getDBInstaller( $type );
00362 
00363                         if ( !$installer->isCompiled() ) {
00364                                 continue;
00365                         }
00366                         $compiledDBs[] = $type;
00367 
00368                         $defaults = $installer->getGlobalDefaults();
00369 
00370                         foreach ( $installer->getGlobalNames() as $var ) {
00371                                 if ( isset( $defaults[$var] ) ) {
00372                                         $this->settings[$var] = $defaults[$var];
00373                                 } else {
00374                                         $this->settings[$var] = $GLOBALS[$var];
00375                                 }
00376                         }
00377                 }
00378                 $this->compiledDBs = $compiledDBs;
00379 
00380                 $this->parserTitle = Title::newFromText( 'Installer' );
00381                 $this->parserOptions = new ParserOptions; // language will  be wrong :(
00382                 $this->parserOptions->setEditSection( false );
00383         }
00384 
00390         public static function getDBTypes() {
00391                 return self::$dbTypes;
00392         }
00393 
00407         public function doEnvironmentChecks() {
00408                 $phpVersion = phpversion();
00409                 if( version_compare( $phpVersion, self::MINIMUM_PHP_VERSION, '>=' ) ) {
00410                         $this->showMessage( 'config-env-php', $phpVersion );
00411                         $good = true;
00412                 } else {
00413                         $this->showMessage( 'config-env-php-toolow', $phpVersion, self::MINIMUM_PHP_VERSION );
00414                         $good = false;
00415                 }
00416 
00417                 if( $good ) {
00418                         foreach ( $this->envChecks as $check ) {
00419                                 $status = $this->$check();
00420                                 if ( $status === false ) {
00421                                         $good = false;
00422                                 }
00423                         }
00424                 }
00425 
00426                 $this->setVar( '_Environment', $good );
00427 
00428                 return $good ? Status::newGood() : Status::newFatal( 'config-env-bad' );
00429         }
00430 
00437         public function setVar( $name, $value ) {
00438                 $this->settings[$name] = $value;
00439         }
00440 
00451         public function getVar( $name, $default = null ) {
00452                 if ( !isset( $this->settings[$name] ) ) {
00453                         return $default;
00454                 } else {
00455                         return $this->settings[$name];
00456                 }
00457         }
00458 
00464         public function getCompiledDBs() {
00465                 return $this->compiledDBs;
00466         }
00467 
00475         public function getDBInstaller( $type = false ) {
00476                 if ( !$type ) {
00477                         $type = $this->getVar( 'wgDBtype' );
00478                 }
00479 
00480                 $type = strtolower( $type );
00481 
00482                 if ( !isset( $this->dbInstallers[$type] ) ) {
00483                         $class = ucfirst( $type ). 'Installer';
00484                         $this->dbInstallers[$type] = new $class( $this );
00485                 }
00486 
00487                 return $this->dbInstallers[$type];
00488         }
00489 
00496         public static function getExistingLocalSettings() {
00497                 global $IP;
00498 
00499                 wfSuppressWarnings();
00500                 $_lsExists = file_exists( "$IP/LocalSettings.php" );
00501                 wfRestoreWarnings();
00502 
00503                 if( !$_lsExists ) {
00504                         return false;
00505                 }
00506                 unset( $_lsExists );
00507 
00508                 require( "$IP/includes/DefaultSettings.php" );
00509                 require( "$IP/LocalSettings.php" );
00510                 if ( file_exists( "$IP/AdminSettings.php" ) ) {
00511                         require( "$IP/AdminSettings.php" );
00512                 }
00513                 return get_defined_vars();
00514         }
00515 
00525         public function getFakePassword( $realPassword ) {
00526                 return str_repeat( '*', strlen( $realPassword ) );
00527         }
00528 
00536         public function setPassword( $name, $value ) {
00537                 if ( !preg_match( '/^\*+$/', $value ) ) {
00538                         $this->setVar( $name, $value );
00539                 }
00540         }
00541 
00553         public static function maybeGetWebserverPrimaryGroup() {
00554                 if ( !function_exists( 'posix_getegid' ) || !function_exists( 'posix_getpwuid' ) ) {
00555                         # I don't know this, this isn't UNIX.
00556                         return null;
00557                 }
00558 
00559                 # posix_getegid() *not* getmygid() because we want the group of the webserver,
00560                 # not whoever owns the current script.
00561                 $gid = posix_getegid();
00562                 $getpwuid = posix_getpwuid( $gid );
00563                 $group = $getpwuid['name'];
00564 
00565                 return $group;
00566         }
00567 
00584         public function parse( $text, $lineStart = false ) {
00585                 global $wgParser;
00586 
00587                 try {
00588                         $out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
00589                         $html = $out->getText();
00590                 } catch ( DBAccessError $e ) {
00591                         $html = '<!--DB access attempted during parse-->  ' . htmlspecialchars( $text );
00592 
00593                         if ( !empty( $this->debug ) ) {
00594                                 $html .= "<!--\n" . $e->getTraceAsString() . "\n-->";
00595                         }
00596                 }
00597 
00598                 return $html;
00599         }
00600 
00604         public function getParserOptions() {
00605                 return $this->parserOptions;
00606         }
00607 
00608         public function disableLinkPopups() {
00609                 $this->parserOptions->setExternalLinkTarget( false );
00610         }
00611 
00612         public function restoreLinkPopups() {
00613                 global $wgExternalLinkTarget;
00614                 $this->parserOptions->setExternalLinkTarget( $wgExternalLinkTarget );
00615         }
00616 
00625         public function populateSiteStats( DatabaseInstaller $installer ) {
00626                 $status = $installer->getConnection();
00627                 if ( !$status->isOK() ) {
00628                         return $status;
00629                 }
00630                 $status->value->insert( 'site_stats', array(
00631                         'ss_row_id' => 1,
00632                         'ss_total_views' => 0,
00633                         'ss_total_edits' => 0,
00634                         'ss_good_articles' => 0,
00635                         'ss_total_pages' => 0,
00636                         'ss_users' => 0,
00637                         'ss_images' => 0 ),
00638                         __METHOD__, 'IGNORE' );
00639                 return Status::newGood();
00640         }
00641 
00645         public function exportVars() {
00646                 foreach ( $this->settings as $name => $value ) {
00647                         if ( substr( $name, 0, 2 ) == 'wg' ) {
00648                                 $GLOBALS[$name] = $value;
00649                         }
00650                 }
00651         }
00652 
00657         protected function envCheckDB() {
00658                 global $wgLang;
00659 
00660                 $allNames = array();
00661 
00662                 foreach ( self::getDBTypes() as $name ) {
00663                         $allNames[] = wfMessage( "config-type-$name" )->text();
00664                 }
00665 
00666                 $databases = $this->getCompiledDBs();
00667 
00668                 $databases = array_flip ( $databases );
00669                 foreach ( array_keys( $databases ) as $db ) {
00670                         $installer = $this->getDBInstaller( $db );
00671                         $status = $installer->checkPrerequisites();
00672                         if ( !$status->isGood() ) {
00673                                 $this->showStatusMessage( $status );
00674                         }
00675                         if ( !$status->isOK() ) {
00676                                 unset( $databases[$db] );
00677                         }
00678                 }
00679                 $databases = array_flip( $databases );
00680                 if ( !$databases ) {
00681                         $this->showError( 'config-no-db', $wgLang->commaList( $allNames ) );
00682                         // @todo FIXME: This only works for the web installer!
00683                         return false;
00684                 }
00685                 return true;
00686         }
00687 
00691         protected function envCheckRegisterGlobals() {
00692                 if( wfIniGetBool( 'register_globals' ) ) {
00693                         $this->showMessage( 'config-register-globals' );
00694                 }
00695         }
00696 
00701         protected function envCheckBrokenXML() {
00702                 $test = new PhpXmlBugTester();
00703                 if ( !$test->ok ) {
00704                         $this->showError( 'config-brokenlibxml' );
00705                         return false;
00706                 }
00707                 return true;
00708         }
00709 
00715         protected function envCheckPHP531() {
00716                 $test = new PhpRefCallBugTester;
00717                 $test->execute();
00718                 if ( !$test->ok ) {
00719                         $this->showError( 'config-using531', phpversion() );
00720                         return false;
00721                 }
00722                 return true;
00723         }
00724 
00729         protected function envCheckMagicQuotes() {
00730                 if( wfIniGetBool( "magic_quotes_runtime" ) ) {
00731                         $this->showError( 'config-magic-quotes-runtime' );
00732                         return false;
00733                 }
00734                 return true;
00735         }
00736 
00741         protected function envCheckMagicSybase() {
00742                 if ( wfIniGetBool( 'magic_quotes_sybase' ) ) {
00743                         $this->showError( 'config-magic-quotes-sybase' );
00744                         return false;
00745                 }
00746                 return true;
00747         }
00748 
00753         protected function envCheckMbstring() {
00754                 if ( wfIniGetBool( 'mbstring.func_overload' ) ) {
00755                         $this->showError( 'config-mbstring' );
00756                         return false;
00757                 }
00758                 return true;
00759         }
00760 
00765         protected function envCheckZE1() {
00766                 if ( wfIniGetBool( 'zend.ze1_compatibility_mode' ) ) {
00767                         $this->showError( 'config-ze1' );
00768                         return false;
00769                 }
00770                 return true;
00771         }
00772 
00777         protected function envCheckSafeMode() {
00778                 if ( wfIniGetBool( 'safe_mode' ) ) {
00779                         $this->setVar( '_SafeMode', true );
00780                         $this->showMessage( 'config-safe-mode' );
00781                 }
00782                 return true;
00783         }
00784 
00789         protected function envCheckXML() {
00790                 if ( !function_exists( "utf8_encode" ) ) {
00791                         $this->showError( 'config-xml-bad' );
00792                         return false;
00793                 }
00794                 return true;
00795         }
00796 
00805         protected function envCheckPCRE() {
00806                 if ( !function_exists( 'preg_match' ) ) {
00807                         $this->showError( 'config-pcre' );
00808                         return false;
00809                 }
00810                 wfSuppressWarnings();
00811                 $regexd = preg_replace( '/[\x{0430}-\x{04FF}]/iu', '', '-АБВГД-' );
00812                 // Need to check for \p support too, as PCRE can be compiled
00813                 // with utf8 support, but not unicode property support.
00814                 // check that \p{Zs} (space separators) matches
00815                 // U+3000 (Ideographic space)
00816                 $regexprop = preg_replace( '/\p{Zs}/u', '', "-\xE3\x80\x80-" );
00817                 wfRestoreWarnings();
00818                 if ( $regexd != '--' || $regexprop != '--' ) {
00819                         $this->showError( 'config-pcre-no-utf8' );
00820                         return false;
00821                 }
00822                 return true;
00823         }
00824 
00829         protected function envCheckMemory() {
00830                 $limit = ini_get( 'memory_limit' );
00831 
00832                 if ( !$limit || $limit == -1 ) {
00833                         return true;
00834                 }
00835 
00836                 $n = wfShorthandToInteger( $limit );
00837 
00838                 if( $n < $this->minMemorySize * 1024 * 1024 ) {
00839                         $newLimit = "{$this->minMemorySize}M";
00840 
00841                         if( ini_set( "memory_limit", $newLimit ) === false ) {
00842                                 $this->showMessage( 'config-memory-bad', $limit );
00843                         } else {
00844                                 $this->showMessage( 'config-memory-raised', $limit, $newLimit );
00845                                 $this->setVar( '_RaiseMemory', true );
00846                         }
00847                 }
00848                 return true;
00849         }
00850 
00854         protected function envCheckCache() {
00855                 $caches = array();
00856                 foreach ( $this->objectCaches as $name => $function ) {
00857                         if ( function_exists( $function ) ) {
00858                                 if ( $name == 'xcache' && !wfIniGetBool( 'xcache.var_size' ) ) {
00859                                         continue;
00860                                 }
00861                                 $caches[$name] = true;
00862                         }
00863                 }
00864 
00865                 if ( !$caches ) {
00866                         $this->showMessage( 'config-no-cache' );
00867                 }
00868 
00869                 $this->setVar( '_Caches', $caches );
00870         }
00871 
00876         protected function envCheckModSecurity() {
00877                 if ( self::apacheModulePresent( 'mod_security' ) ) {
00878                         $this->showMessage( 'config-mod-security' );
00879                 }
00880                 return true;
00881         }
00882 
00887         protected function envCheckDiff3() {
00888                 $names = array( "gdiff3", "diff3", "diff3.exe" );
00889                 $versionInfo = array( '$1 --version 2>&1', 'GNU diffutils' );
00890 
00891                 $diff3 = self::locateExecutableInDefaultPaths( $names, $versionInfo );
00892 
00893                 if ( $diff3 ) {
00894                         $this->setVar( 'wgDiff3', $diff3 );
00895                 } else {
00896                         $this->setVar( 'wgDiff3', false );
00897                         $this->showMessage( 'config-diff3-bad' );
00898                 }
00899                 return true;
00900         }
00901 
00906         protected function envCheckGraphics() {
00907                 $names = array( wfIsWindows() ? 'convert.exe' : 'convert' );
00908                 $convert = self::locateExecutableInDefaultPaths( $names, array( '$1 -version', 'ImageMagick' ) );
00909 
00910                 $this->setVar( 'wgImageMagickConvertCommand', '' );
00911                 if ( $convert ) {
00912                         $this->setVar( 'wgImageMagickConvertCommand', $convert );
00913                         $this->showMessage( 'config-imagemagick', $convert );
00914                         return true;
00915                 } elseif ( function_exists( 'imagejpeg' ) ) {
00916                         $this->showMessage( 'config-gd' );
00917 
00918                 } else {
00919                         $this->showMessage( 'config-no-scaling' );
00920                 }
00921                 return true;
00922         }
00923 
00927         protected function envCheckServer() {
00928                 $server = $this->envGetDefaultServer();
00929                 $this->showMessage( 'config-using-server', $server );
00930                 $this->setVar( 'wgServer', $server );
00931                 return true;
00932         }
00933 
00938         abstract protected function envGetDefaultServer();
00939 
00944         protected function envCheckPath() {
00945                 global $IP;
00946                 $IP = dirname( dirname( __DIR__ ) );
00947                 $this->setVar( 'IP', $IP );
00948 
00949                 $this->showMessage( 'config-using-uri', $this->getVar( 'wgServer' ), $this->getVar( 'wgScriptPath' ) );
00950                 return true;
00951         }
00952 
00956         protected function envCheckExtension() {
00957                 // @todo FIXME: Detect this properly
00958                 if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) {
00959                         $ext = 'php5';
00960                 } else {
00961                         $ext = 'php';
00962                 }
00963                 $this->setVar( 'wgScriptExtension', ".$ext" );
00964                 return true;
00965         }
00966 
00971         protected function envCheckShellLocale() {
00972                 $os = php_uname( 's' );
00973                 $supported = array( 'Linux', 'SunOS', 'HP-UX', 'Darwin' ); # Tested these
00974 
00975                 if ( !in_array( $os, $supported ) ) {
00976                         return true;
00977                 }
00978 
00979                 # Get a list of available locales.
00980                 $ret = false;
00981                 $lines = wfShellExec( '/usr/bin/locale -a', $ret );
00982 
00983                 if ( $ret ) {
00984                         return true;
00985                 }
00986 
00987                 $lines = wfArrayMap( 'trim', explode( "\n", $lines ) );
00988                 $candidatesByLocale = array();
00989                 $candidatesByLang = array();
00990 
00991                 foreach ( $lines as $line ) {
00992                         if ( $line === '' ) {
00993                                 continue;
00994                         }
00995 
00996                         if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) {
00997                                 continue;
00998                         }
00999 
01000                         list( , $lang, , , ) = $m;
01001 
01002                         $candidatesByLocale[$m[0]] = $m;
01003                         $candidatesByLang[$lang][] = $m;
01004                 }
01005 
01006                 # Try the current value of LANG.
01007                 if ( isset( $candidatesByLocale[ getenv( 'LANG' ) ] ) ) {
01008                         $this->setVar( 'wgShellLocale', getenv( 'LANG' ) );
01009                         return true;
01010                 }
01011 
01012                 # Try the most common ones.
01013                 $commonLocales = array( 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' );
01014                 foreach ( $commonLocales as $commonLocale ) {
01015                         if ( isset( $candidatesByLocale[$commonLocale] ) ) {
01016                                 $this->setVar( 'wgShellLocale', $commonLocale );
01017                                 return true;
01018                         }
01019                 }
01020 
01021                 # Is there an available locale in the Wiki's language?
01022                 $wikiLang = $this->getVar( 'wgLanguageCode' );
01023 
01024                 if ( isset( $candidatesByLang[$wikiLang] ) ) {
01025                         $m = reset( $candidatesByLang[$wikiLang] );
01026                         $this->setVar( 'wgShellLocale', $m[0] );
01027                         return true;
01028                 }
01029 
01030                 # Are there any at all?
01031                 if ( count( $candidatesByLocale ) ) {
01032                         $m = reset( $candidatesByLocale );
01033                         $this->setVar( 'wgShellLocale', $m[0] );
01034                         return true;
01035                 }
01036 
01037                 # Give up.
01038                 return true;
01039         }
01040 
01045         protected function envCheckUploadsDirectory() {
01046                 global $IP;
01047 
01048                 $dir = $IP . '/images/';
01049                 $url = $this->getVar( 'wgServer' ) . $this->getVar( 'wgScriptPath' ) . '/images/';
01050                 $safe = !$this->dirIsExecutable( $dir, $url );
01051 
01052                 if ( !$safe ) {
01053                         $this->showMessage( 'config-uploads-not-safe', $dir );
01054                 }
01055                 return true;
01056         }
01057 
01064         protected function envCheckSuhosinMaxValueLength() {
01065                 $maxValueLength = ini_get( 'suhosin.get.max_value_length' );
01066                 if ( $maxValueLength > 0 ) {
01067                         if( $maxValueLength < 1024 ) {
01068                                 # Only warn if the value is below the sane 1024
01069                                 $this->showMessage( 'config-suhosin-max-value-length', $maxValueLength );
01070                         }
01071                 } else {
01072                         $maxValueLength = -1;
01073                 }
01074                 $this->setVar( 'wgResourceLoaderMaxQueryLength', $maxValueLength );
01075                 return true;
01076         }
01077 
01083         protected function unicodeChar( $c ) {
01084                 $c = hexdec( $c );
01085                 if ( $c <= 0x7F ) {
01086                         return chr( $c );
01087                 } elseif ( $c <= 0x7FF ) {
01088                         return chr( 0xC0 | $c >> 6 ) . chr( 0x80 | $c & 0x3F );
01089                 } elseif ( $c <= 0xFFFF ) {
01090                         return chr( 0xE0 | $c >> 12 ) . chr( 0x80 | $c >> 6 & 0x3F )
01091                                 . chr( 0x80 | $c & 0x3F );
01092                 } elseif ( $c <= 0x10FFFF ) {
01093                         return chr( 0xF0 | $c >> 18 ) . chr( 0x80 | $c >> 12 & 0x3F )
01094                                 . chr( 0x80 | $c >> 6 & 0x3F )
01095                                 . chr( 0x80 | $c & 0x3F );
01096                 } else {
01097                         return false;
01098                 }
01099         }
01100 
01104         protected function envCheckLibicu() {
01105                 $utf8 = function_exists( 'utf8_normalize' );
01106                 $intl = function_exists( 'normalizer_normalize' );
01107 
01115                 $not_normal_c = $this->unicodeChar( "FA6C" );
01116                 $normal_c = $this->unicodeChar( "242EE" );
01117 
01118                 $useNormalizer = 'php';
01119                 $needsUpdate = false;
01120 
01125                 if( $utf8 ) {
01126                         $useNormalizer = 'utf8';
01127                         $utf8 = utf8_normalize( $not_normal_c, UtfNormal::UNORM_NFC );
01128                         if ( $utf8 !== $normal_c ) {
01129                                 $needsUpdate = true;
01130                         }
01131                 }
01132                 if( $intl ) {
01133                         $useNormalizer = 'intl';
01134                         $intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C );
01135                         if ( $intl !== $normal_c ) {
01136                                 $needsUpdate = true;
01137                         }
01138                 }
01139 
01140                 // Uses messages 'config-unicode-using-php', 'config-unicode-using-utf8', 'config-unicode-using-intl'
01141                 if( $useNormalizer === 'php' ) {
01142                         $this->showMessage( 'config-unicode-pure-php-warning' );
01143                 } else {
01144                         $this->showMessage( 'config-unicode-using-' . $useNormalizer );
01145                         if( $needsUpdate ) {
01146                                 $this->showMessage( 'config-unicode-update-warning' );
01147                         }
01148                 }
01149         }
01150 
01154         protected function envCheckCtype() {
01155                 if ( !function_exists( 'ctype_digit' ) ) {
01156                         $this->showError( 'config-ctype' );
01157                         return false;
01158                 }
01159                 return true;
01160         }
01161 
01169         protected static function getPossibleBinPaths() {
01170                 return array_merge(
01171                         array( '/usr/bin', '/usr/local/bin', '/opt/csw/bin',
01172                                 '/usr/gnu/bin', '/usr/sfw/bin', '/sw/bin', '/opt/local/bin' ),
01173                         explode( PATH_SEPARATOR, getenv( 'PATH' ) )
01174                 );
01175         }
01176 
01194         public static function locateExecutable( $path, $names, $versionInfo = false ) {
01195                 if ( !is_array( $names ) ) {
01196                         $names = array( $names );
01197                 }
01198 
01199                 foreach ( $names as $name ) {
01200                         $command = $path . DIRECTORY_SEPARATOR . $name;
01201 
01202                         wfSuppressWarnings();
01203                         $file_exists = file_exists( $command );
01204                         wfRestoreWarnings();
01205 
01206                         if ( $file_exists ) {
01207                                 if ( !$versionInfo ) {
01208                                         return $command;
01209                                 }
01210 
01211                                 $file = str_replace( '$1', wfEscapeShellArg( $command ), $versionInfo[0] );
01212                                 if ( strstr( wfShellExec( $file ), $versionInfo[1] ) !== false ) {
01213                                         return $command;
01214                                 }
01215                         }
01216                 }
01217                 return false;
01218         }
01219 
01227         public static function locateExecutableInDefaultPaths( $names, $versionInfo = false ) {
01228                 foreach( self::getPossibleBinPaths() as $path ) {
01229                         $exe = self::locateExecutable( $path, $names, $versionInfo );
01230                         if( $exe !== false ) {
01231                                 return $exe;
01232                         }
01233                 }
01234                 return false;
01235         }
01236 
01245         public function dirIsExecutable( $dir, $url ) {
01246                 $scriptTypes = array(
01247                         'php' => array(
01248                                 "<?php echo 'ex' . 'ec';",
01249                                 "#!/var/env php5\n<?php echo 'ex' . 'ec';",
01250                         ),
01251                 );
01252 
01253                 // it would be good to check other popular languages here, but it'll be slow.
01254 
01255                 wfSuppressWarnings();
01256 
01257                 foreach ( $scriptTypes as $ext => $contents ) {
01258                         foreach ( $contents as $source ) {
01259                                 $file = 'exectest.' . $ext;
01260 
01261                                 if ( !file_put_contents( $dir . $file, $source ) ) {
01262                                         break;
01263                                 }
01264 
01265                                 try {
01266                                         $text = Http::get( $url . $file, array( 'timeout' => 3 ) );
01267                                 }
01268                                 catch( MWException $e ) {
01269                                         // Http::get throws with allow_url_fopen = false and no curl extension.
01270                                         $text = null;
01271                                 }
01272                                 unlink( $dir . $file );
01273 
01274                                 if ( $text == 'exec' ) {
01275                                         wfRestoreWarnings();
01276                                         return $ext;
01277                                 }
01278                         }
01279                 }
01280 
01281                 wfRestoreWarnings();
01282 
01283                 return false;
01284         }
01285 
01292         public static function apacheModulePresent( $moduleName ) {
01293                 if ( function_exists( 'apache_get_modules' ) && in_array( $moduleName, apache_get_modules() ) ) {
01294                         return true;
01295                 }
01296                 // try it the hard way
01297                 ob_start();
01298                 phpinfo( INFO_MODULES );
01299                 $info = ob_get_clean();
01300                 return strpos( $info, $moduleName ) !== false;
01301         }
01302 
01308         public function setParserLanguage( $lang ) {
01309                 $this->parserOptions->setTargetLanguage( $lang );
01310                 $this->parserOptions->setUserLang( $lang );
01311         }
01312 
01318         protected function getDocUrl( $page ) {
01319                 return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page );
01320         }
01321 
01328         public function findExtensions() {
01329                 if( $this->getVar( 'IP' ) === null ) {
01330                         return false;
01331                 }
01332 
01333                 $exts = array();
01334                 $extDir = $this->getVar( 'IP' ) . '/extensions';
01335                 $dh = opendir( $extDir );
01336 
01337                 while ( ( $file = readdir( $dh ) ) !== false ) {
01338                         if( !is_dir( "$extDir/$file" ) ) {
01339                                 continue;
01340                         }
01341                         if( file_exists( "$extDir/$file/$file.php" ) ) {
01342                                 $exts[] = $file;
01343                         }
01344                 }
01345                 natcasesort( $exts );
01346 
01347                 return $exts;
01348         }
01349 
01355         protected function includeExtensions() {
01356                 global $IP;
01357                 $exts = $this->getVar( '_Extensions' );
01358                 $IP = $this->getVar( 'IP' );
01359 
01368                 global $wgAutoloadClasses;
01369                 $wgAutoloadClasses = array();
01370 
01371                 require( "$IP/includes/DefaultSettings.php" );
01372 
01373                 foreach( $exts as $e ) {
01374                         require_once( "$IP/extensions/$e/$e.php" );
01375                 }
01376 
01377                 $hooksWeWant = isset( $wgHooks['LoadExtensionSchemaUpdates'] ) ?
01378                         $wgHooks['LoadExtensionSchemaUpdates'] : array();
01379 
01380                 // Unset everyone else's hooks. Lord knows what someone might be doing
01381                 // in ParserFirstCallInit (see bug 27171)
01382                 $GLOBALS['wgHooks'] = array( 'LoadExtensionSchemaUpdates' => $hooksWeWant );
01383 
01384                 return Status::newGood();
01385         }
01386 
01399         protected function getInstallSteps( DatabaseInstaller $installer ) {
01400                 $coreInstallSteps = array(
01401                         array( 'name' => 'database',   'callback' => array( $installer, 'setupDatabase' ) ),
01402                         array( 'name' => 'tables',     'callback' => array( $installer, 'createTables' ) ),
01403                         array( 'name' => 'interwiki',  'callback' => array( $installer, 'populateInterwikiTable' ) ),
01404                         array( 'name' => 'stats',      'callback' => array( $this, 'populateSiteStats' ) ),
01405                         array( 'name' => 'keys',       'callback' => array( $this, 'generateKeys' ) ),
01406                         array( 'name' => 'sysop',      'callback' => array( $this, 'createSysop' ) ),
01407                         array( 'name' => 'mainpage',   'callback' => array( $this, 'createMainpage' ) ),
01408                 );
01409 
01410                 // Build the array of install steps starting from the core install list,
01411                 // then adding any callbacks that wanted to attach after a given step
01412                 foreach( $coreInstallSteps as $step ) {
01413                         $this->installSteps[] = $step;
01414                         if( isset( $this->extraInstallSteps[ $step['name'] ] ) ) {
01415                                 $this->installSteps = array_merge(
01416                                         $this->installSteps,
01417                                         $this->extraInstallSteps[ $step['name'] ]
01418                                 );
01419                         }
01420                 }
01421 
01422                 // Prepend any steps that want to be at the beginning
01423                 if( isset( $this->extraInstallSteps['BEGINNING'] ) ) {
01424                         $this->installSteps = array_merge(
01425                                 $this->extraInstallSteps['BEGINNING'],
01426                                 $this->installSteps
01427                         );
01428                 }
01429 
01430                 // Extensions should always go first, chance to tie into hooks and such
01431                 if( count( $this->getVar( '_Extensions' ) ) ) {
01432                         array_unshift( $this->installSteps,
01433                                 array( 'name' => 'extensions', 'callback' => array( $this, 'includeExtensions' ) )
01434                         );
01435                         $this->installSteps[] = array(
01436                                 'name' => 'extension-tables',
01437                                 'callback' => array( $installer, 'createExtensionTables' )
01438                         );
01439                 }
01440                 return $this->installSteps;
01441         }
01442 
01451         public function performInstallation( $startCB, $endCB ) {
01452                 $installResults = array();
01453                 $installer = $this->getDBInstaller();
01454                 $installer->preInstall();
01455                 $steps = $this->getInstallSteps( $installer );
01456                 foreach( $steps as $stepObj ) {
01457                         $name = $stepObj['name'];
01458                         call_user_func_array( $startCB, array( $name ) );
01459 
01460                         // Perform the callback step
01461                         $status = call_user_func( $stepObj['callback'], $installer );
01462 
01463                         // Output and save the results
01464                         call_user_func( $endCB, $name, $status );
01465                         $installResults[$name] = $status;
01466 
01467                         // If we've hit some sort of fatal, we need to bail.
01468                         // Callback already had a chance to do output above.
01469                         if( !$status->isOk() ) {
01470                                 break;
01471                         }
01472                 }
01473                 if( $status->isOk() ) {
01474                         $this->setVar( '_InstallDone', true );
01475                 }
01476                 return $installResults;
01477         }
01478 
01484         public function generateKeys() {
01485                 $keys = array( 'wgSecretKey' => 64 );
01486                 if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) {
01487                         $keys['wgUpgradeKey'] = 16;
01488                 }
01489                 return $this->doGenerateKeys( $keys );
01490         }
01491 
01499         protected function doGenerateKeys( $keys ) {
01500                 $status = Status::newGood();
01501 
01502                 $strong = true;
01503                 foreach ( $keys as $name => $length ) {
01504                         $secretKey = MWCryptRand::generateHex( $length, true );
01505                         if ( !MWCryptRand::wasStrong() ) {
01506                                 $strong = false;
01507                         }
01508 
01509                         $this->setVar( $name, $secretKey );
01510                 }
01511 
01512                 if ( !$strong ) {
01513                         $names = array_keys( $keys );
01514                         $names = preg_replace( '/^(.*)$/', '\$$1', $names );
01515                         global $wgLang;
01516                         $status->warning( 'config-insecure-keys', $wgLang->listToText( $names ), count( $names ) );
01517                 }
01518 
01519                 return $status;
01520         }
01521 
01527         protected function createSysop() {
01528                 $name = $this->getVar( '_AdminName' );
01529                 $user = User::newFromName( $name );
01530 
01531                 if ( !$user ) {
01532                         // We should've validated this earlier anyway!
01533                         return Status::newFatal( 'config-admin-error-user', $name );
01534                 }
01535 
01536                 if ( $user->idForName() == 0 ) {
01537                         $user->addToDatabase();
01538 
01539                         try {
01540                                 $user->setPassword( $this->getVar( '_AdminPassword' ) );
01541                         } catch( PasswordError $pwe ) {
01542                                 return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() );
01543                         }
01544 
01545                         $user->addGroup( 'sysop' );
01546                         $user->addGroup( 'bureaucrat' );
01547                         if( $this->getVar( '_AdminEmail' ) ) {
01548                                 $user->setEmail( $this->getVar( '_AdminEmail' ) );
01549                         }
01550                         $user->saveSettings();
01551 
01552                         // Update user count
01553                         $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 );
01554                         $ssUpdate->doUpdate();
01555                 }
01556                 $status = Status::newGood();
01557 
01558                 if( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) {
01559                         $this->subscribeToMediaWikiAnnounce( $status );
01560                 }
01561 
01562                 return $status;
01563         }
01564 
01568         private function subscribeToMediaWikiAnnounce( Status $s ) {
01569                 $params = array(
01570                         'email'    => $this->getVar( '_AdminEmail' ),
01571                         'language' => 'en',
01572                         'digest'   => 0
01573                 );
01574 
01575                 // Mailman doesn't support as many languages as we do, so check to make
01576                 // sure their selected language is available
01577                 $myLang = $this->getVar( '_UserLang' );
01578                 if( in_array( $myLang, $this->mediaWikiAnnounceLanguages ) ) {
01579                         $myLang = $myLang == 'pt-br' ? 'pt_BR' : $myLang; // rewrite to Mailman's pt_BR
01580                         $params['language'] = $myLang;
01581                 }
01582 
01583                 if( MWHttpRequest::canMakeRequests() ) {
01584                         $res = MWHttpRequest::factory( $this->mediaWikiAnnounceUrl,
01585                                 array( 'method' => 'POST', 'postData' => $params ) )->execute();
01586                         if( !$res->isOK() ) {
01587                                 $s->warning( 'config-install-subscribe-fail', $res->getMessage() );
01588                         }
01589                 } else {
01590                         $s->warning( 'config-install-subscribe-notpossible' );
01591                 }
01592         }
01593 
01600         protected function createMainpage( DatabaseInstaller $installer ) {
01601                 $status = Status::newGood();
01602                 try {
01603                         $page = WikiPage::factory( Title::newMainPage() );
01604                         $content = new WikitextContent (
01605                                 wfMessage( 'mainpagetext' )->inContentLanguage()->text() . "\n\n" .
01606                                 wfMessage( 'mainpagedocfooter' )->inContentLanguage()->text()
01607                         );
01608 
01609                         $page->doEditContent( $content,
01610                                         '',
01611                                         EDIT_NEW,
01612                                         false,
01613                                         User::newFromName( 'MediaWiki default' ) );
01614                 } catch (MWException $e) {
01615                         //using raw, because $wgShowExceptionDetails can not be set yet
01616                         $status->fatal( 'config-install-mainpage-failed', $e->getMessage() );
01617                 }
01618 
01619                 return $status;
01620         }
01621 
01625         public static function overrideConfig() {
01626                 define( 'MW_NO_SESSION', 1 );
01627 
01628                 // Don't access the database
01629                 $GLOBALS['wgUseDatabaseMessages'] = false;
01630                 // Don't cache langconv tables
01631                 $GLOBALS['wgLanguageConverterCacheType'] = CACHE_NONE;
01632                 // Debug-friendly
01633                 $GLOBALS['wgShowExceptionDetails'] = true;
01634                 // Don't break forms
01635                 $GLOBALS['wgExternalLinkTarget'] = '_blank';
01636 
01637                 // Extended debugging
01638                 $GLOBALS['wgShowSQLErrors'] = true;
01639                 $GLOBALS['wgShowDBErrorBacktrace'] = true;
01640 
01641                 // Allow multiple ob_flush() calls
01642                 $GLOBALS['wgDisableOutputCompression'] = true;
01643 
01644                 // Use a sensible cookie prefix (not my_wiki)
01645                 $GLOBALS['wgCookiePrefix'] = 'mw_installer';
01646 
01647                 // Some of the environment checks make shell requests, remove limits
01648                 $GLOBALS['wgMaxShellMemory'] = 0;
01649         }
01650 
01658         public function addInstallStep( $callback, $findStep = 'BEGINNING' ) {
01659                 $this->extraInstallSteps[$findStep][] = $callback;
01660         }
01661 
01666         protected function disableTimeLimit() {
01667                 wfSuppressWarnings();
01668                 set_time_limit( 0 );
01669                 wfRestoreWarnings();
01670         }
01671 }