[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/installer/ -> Installer.php (source)

   1  <?php
   2  /**
   3   * Base code for MediaWiki installer.
   4   *
   5   * This program is free software; you can redistribute it and/or modify
   6   * it under the terms of the GNU General Public License as published by
   7   * the Free Software Foundation; either version 2 of the License, or
   8   * (at your option) any later version.
   9   *
  10   * This program is distributed in the hope that it will be useful,
  11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13   * GNU General Public License for more details.
  14   *
  15   * You should have received a copy of the GNU General Public License along
  16   * with this program; if not, write to the Free Software Foundation, Inc.,
  17   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18   * http://www.gnu.org/copyleft/gpl.html
  19   *
  20   * @file
  21   * @ingroup Deployment
  22   */
  23  
  24  /**
  25   * This documentation group collects source code files with deployment functionality.
  26   *
  27   * @defgroup Deployment Deployment
  28   */
  29  
  30  /**
  31   * Base installer class.
  32   *
  33   * This class provides the base for installation and update functionality
  34   * for both MediaWiki core and extensions.
  35   *
  36   * @ingroup Deployment
  37   * @since 1.17
  38   */
  39  abstract class Installer {
  40  
  41      /**
  42       * The oldest version of PCRE we can support.
  43       *
  44       * Defining this is necessary because PHP may be linked with a system version
  45       * of PCRE, which may be older than that bundled with the minimum PHP version.
  46       */
  47      const MINIMUM_PCRE_VERSION = '7.2';
  48  
  49      /**
  50       * @var array
  51       */
  52      protected $settings;
  53  
  54      /**
  55       * List of detected DBs, access using getCompiledDBs().
  56       *
  57       * @var array
  58       */
  59      protected $compiledDBs;
  60  
  61      /**
  62       * Cached DB installer instances, access using getDBInstaller().
  63       *
  64       * @var array
  65       */
  66      protected $dbInstallers = array();
  67  
  68      /**
  69       * Minimum memory size in MB.
  70       *
  71       * @var int
  72       */
  73      protected $minMemorySize = 50;
  74  
  75      /**
  76       * Cached Title, used by parse().
  77       *
  78       * @var Title
  79       */
  80      protected $parserTitle;
  81  
  82      /**
  83       * Cached ParserOptions, used by parse().
  84       *
  85       * @var ParserOptions
  86       */
  87      protected $parserOptions;
  88  
  89      /**
  90       * Known database types. These correspond to the class names <type>Installer,
  91       * and are also MediaWiki database types valid for $wgDBtype.
  92       *
  93       * To add a new type, create a <type>Installer class and a Database<type>
  94       * class, and add a config-type-<type> message to MessagesEn.php.
  95       *
  96       * @var array
  97       */
  98      protected static $dbTypes = array(
  99          'mysql',
 100          'postgres',
 101          'oracle',
 102          'mssql',
 103          'sqlite',
 104      );
 105  
 106      /**
 107       * A list of environment check methods called by doEnvironmentChecks().
 108       * These may output warnings using showMessage(), and/or abort the
 109       * installation process by returning false.
 110       *
 111       * For the WebInstaller these are only called on the Welcome page,
 112       * if these methods have side-effects that should affect later page loads
 113       * (as well as the generated stylesheet), use envPreps instead.
 114       *
 115       * @var array
 116       */
 117      protected $envChecks = array(
 118          'envCheckDB',
 119          'envCheckRegisterGlobals',
 120          'envCheckBrokenXML',
 121          'envCheckMagicQuotes',
 122          'envCheckMbstring',
 123          'envCheckSafeMode',
 124          'envCheckXML',
 125          'envCheckPCRE',
 126          'envCheckMemory',
 127          'envCheckCache',
 128          'envCheckModSecurity',
 129          'envCheckDiff3',
 130          'envCheckGraphics',
 131          'envCheckGit',
 132          'envCheckServer',
 133          'envCheckPath',
 134          'envCheckShellLocale',
 135          'envCheckUploadsDirectory',
 136          'envCheckLibicu',
 137          'envCheckSuhosinMaxValueLength',
 138          'envCheckCtype',
 139          'envCheckIconv',
 140          'envCheckJSON',
 141      );
 142  
 143      /**
 144       * A list of environment preparation methods called by doEnvironmentPreps().
 145       *
 146       * @var array
 147       */
 148      protected $envPreps = array(
 149          'envPrepExtension',
 150          'envPrepServer',
 151          'envPrepPath',
 152      );
 153  
 154      /**
 155       * MediaWiki configuration globals that will eventually be passed through
 156       * to LocalSettings.php. The names only are given here, the defaults
 157       * typically come from DefaultSettings.php.
 158       *
 159       * @var array
 160       */
 161      protected $defaultVarNames = array(
 162          'wgSitename',
 163          'wgPasswordSender',
 164          'wgLanguageCode',
 165          'wgRightsIcon',
 166          'wgRightsText',
 167          'wgRightsUrl',
 168          'wgMainCacheType',
 169          'wgEnableEmail',
 170          'wgEnableUserEmail',
 171          'wgEnotifUserTalk',
 172          'wgEnotifWatchlist',
 173          'wgEmailAuthentication',
 174          'wgDBtype',
 175          'wgDiff3',
 176          'wgImageMagickConvertCommand',
 177          'wgGitBin',
 178          'IP',
 179          'wgScriptPath',
 180          'wgScriptExtension',
 181          'wgMetaNamespace',
 182          'wgDeletedDirectory',
 183          'wgEnableUploads',
 184          'wgShellLocale',
 185          'wgSecretKey',
 186          'wgUseInstantCommons',
 187          'wgUpgradeKey',
 188          'wgDefaultSkin',
 189          'wgResourceLoaderMaxQueryLength',
 190      );
 191  
 192      /**
 193       * Variables that are stored alongside globals, and are used for any
 194       * configuration of the installation process aside from the MediaWiki
 195       * configuration. Map of names to defaults.
 196       *
 197       * @var array
 198       */
 199      protected $internalDefaults = array(
 200          '_UserLang' => 'en',
 201          '_Environment' => false,
 202          '_SafeMode' => false,
 203          '_RaiseMemory' => false,
 204          '_UpgradeDone' => false,
 205          '_InstallDone' => false,
 206          '_Caches' => array(),
 207          '_InstallPassword' => '',
 208          '_SameAccount' => true,
 209          '_CreateDBAccount' => false,
 210          '_NamespaceType' => 'site-name',
 211          '_AdminName' => '', // will be set later, when the user selects language
 212          '_AdminPassword' => '',
 213          '_AdminPasswordConfirm' => '',
 214          '_AdminEmail' => '',
 215          '_Subscribe' => false,
 216          '_SkipOptional' => 'continue',
 217          '_RightsProfile' => 'wiki',
 218          '_LicenseCode' => 'none',
 219          '_CCDone' => false,
 220          '_Extensions' => array(),
 221          '_Skins' => array(),
 222          '_MemCachedServers' => '',
 223          '_UpgradeKeySupplied' => false,
 224          '_ExistingDBSettings' => false,
 225  
 226          // $wgLogo is probably wrong (bug 48084); set something that will work.
 227          // Single quotes work fine here, as LocalSettingsGenerator outputs this unescaped.
 228          'wgLogo' => '$wgScriptPath/resources/assets/wiki.png',
 229      );
 230  
 231      /**
 232       * The actual list of installation steps. This will be initialized by getInstallSteps()
 233       *
 234       * @var array
 235       */
 236      private $installSteps = array();
 237  
 238      /**
 239       * Extra steps for installation, for things like DatabaseInstallers to modify
 240       *
 241       * @var array
 242       */
 243      protected $extraInstallSteps = array();
 244  
 245      /**
 246       * Known object cache types and the functions used to test for their existence.
 247       *
 248       * @var array
 249       */
 250      protected $objectCaches = array(
 251          'xcache' => 'xcache_get',
 252          'apc' => 'apc_fetch',
 253          'wincache' => 'wincache_ucache_get'
 254      );
 255  
 256      /**
 257       * User rights profiles.
 258       *
 259       * @var array
 260       */
 261      public $rightsProfiles = array(
 262          'wiki' => array(),
 263          'no-anon' => array(
 264              '*' => array( 'edit' => false )
 265          ),
 266          'fishbowl' => array(
 267              '*' => array(
 268                  'createaccount' => false,
 269                  'edit' => false,
 270              ),
 271          ),
 272          'private' => array(
 273              '*' => array(
 274                  'createaccount' => false,
 275                  'edit' => false,
 276                  'read' => false,
 277              ),
 278          ),
 279      );
 280  
 281      /**
 282       * License types.
 283       *
 284       * @var array
 285       */
 286      public $licenses = array(
 287          'cc-by' => array(
 288              'url' => 'http://creativecommons.org/licenses/by/3.0/',
 289              'icon' => '{$wgResourceBasePath}/resources/assets/licenses/cc-by.png',
 290          ),
 291          'cc-by-sa' => array(
 292              'url' => 'http://creativecommons.org/licenses/by-sa/3.0/',
 293              'icon' => '{$wgResourceBasePath}/resources/assets/licenses/cc-by-sa.png',
 294          ),
 295          'cc-by-nc-sa' => array(
 296              'url' => 'http://creativecommons.org/licenses/by-nc-sa/3.0/',
 297              'icon' => '{$wgResourceBasePath}/resources/assets/licenses/cc-by-nc-sa.png',
 298          ),
 299          'cc-0' => array(
 300              'url' => 'https://creativecommons.org/publicdomain/zero/1.0/',
 301              'icon' => '{$wgResourceBasePath}/resources/assets/licenses/cc-0.png',
 302          ),
 303          'pd' => array(
 304              'url' => '',
 305              'icon' => '{$wgResourceBasePath}/resources/assets/licenses/public-domain.png',
 306          ),
 307          'gfdl' => array(
 308              'url' => 'http://www.gnu.org/copyleft/fdl.html',
 309              'icon' => '{$wgResourceBasePath}/resources/assets/licenses/gnu-fdl.png',
 310          ),
 311          'none' => array(
 312              'url' => '',
 313              'icon' => '',
 314              'text' => ''
 315          ),
 316          'cc-choose' => array(
 317              // Details will be filled in by the selector.
 318              'url' => '',
 319              'icon' => '',
 320              'text' => '',
 321          ),
 322      );
 323  
 324      /**
 325       * URL to mediawiki-announce subscription
 326       */
 327      protected $mediaWikiAnnounceUrl =
 328          'https://lists.wikimedia.org/mailman/subscribe/mediawiki-announce';
 329  
 330      /**
 331       * Supported language codes for Mailman
 332       */
 333      protected $mediaWikiAnnounceLanguages = array(
 334          'ca', 'cs', 'da', 'de', 'en', 'es', 'et', 'eu', 'fi', 'fr', 'hr', 'hu',
 335          'it', 'ja', 'ko', 'lt', 'nl', 'no', 'pl', 'pt', 'pt-br', 'ro', 'ru',
 336          'sl', 'sr', 'sv', 'tr', 'uk'
 337      );
 338  
 339      /**
 340       * UI interface for displaying a short message
 341       * The parameters are like parameters to wfMessage().
 342       * The messages will be in wikitext format, which will be converted to an
 343       * output format such as HTML or text before being sent to the user.
 344       * @param string $msg
 345       */
 346      abstract public function showMessage( $msg /*, ... */ );
 347  
 348      /**
 349       * Same as showMessage(), but for displaying errors
 350       * @param string $msg
 351       */
 352      abstract public function showError( $msg /*, ... */ );
 353  
 354      /**
 355       * Show a message to the installing user by using a Status object
 356       * @param Status $status
 357       */
 358      abstract public function showStatusMessage( Status $status );
 359  
 360      /**
 361       * Constructor, always call this from child classes.
 362       */
 363  	public function __construct() {
 364          global $wgMessagesDirs, $wgUser;
 365  
 366          // Disable the i18n cache
 367          Language::getLocalisationCache()->disableBackend();
 368          // Disable LoadBalancer and wfGetDB etc.
 369          LBFactory::disableBackend();
 370  
 371          // Disable object cache (otherwise CACHE_ANYTHING will try CACHE_DB and
 372          // SqlBagOStuff will then throw since we just disabled wfGetDB)
 373          $GLOBALS['wgMemc'] = new EmptyBagOStuff;
 374          ObjectCache::clear();
 375          $emptyCache = array( 'class' => 'EmptyBagOStuff' );
 376          $GLOBALS['wgObjectCaches'] = array(
 377              CACHE_NONE => $emptyCache,
 378              CACHE_DB => $emptyCache,
 379              CACHE_ANYTHING => $emptyCache,
 380              CACHE_MEMCACHED => $emptyCache,
 381          );
 382  
 383          // Load the installer's i18n.
 384          $wgMessagesDirs['MediawikiInstaller'] = __DIR__ . '/i18n';
 385  
 386          // Having a user with id = 0 safeguards us from DB access via User::loadOptions().
 387          $wgUser = User::newFromId( 0 );
 388  
 389          $this->settings = $this->internalDefaults;
 390  
 391          foreach ( $this->defaultVarNames as $var ) {
 392              $this->settings[$var] = $GLOBALS[$var];
 393          }
 394  
 395          $this->doEnvironmentPreps();
 396  
 397          $this->compiledDBs = array();
 398          foreach ( self::getDBTypes() as $type ) {
 399              $installer = $this->getDBInstaller( $type );
 400  
 401              if ( !$installer->isCompiled() ) {
 402                  continue;
 403              }
 404              $this->compiledDBs[] = $type;
 405          }
 406  
 407          $this->parserTitle = Title::newFromText( 'Installer' );
 408          $this->parserOptions = new ParserOptions; // language will be wrong :(
 409          $this->parserOptions->setEditSection( false );
 410      }
 411  
 412      /**
 413       * Get a list of known DB types.
 414       *
 415       * @return array
 416       */
 417  	public static function getDBTypes() {
 418          return self::$dbTypes;
 419      }
 420  
 421      /**
 422       * Do initial checks of the PHP environment. Set variables according to
 423       * the observed environment.
 424       *
 425       * It's possible that this may be called under the CLI SAPI, not the SAPI
 426       * that the wiki will primarily run under. In that case, the subclass should
 427       * initialise variables such as wgScriptPath, before calling this function.
 428       *
 429       * Under the web subclass, it can already be assumed that PHP 5+ is in use
 430       * and that sessions are working.
 431       *
 432       * @return Status
 433       */
 434  	public function doEnvironmentChecks() {
 435          // Php version has already been checked by entry scripts
 436          // Show message here for information purposes
 437          if ( wfIsHHVM() ) {
 438              $this->showMessage( 'config-env-hhvm', HHVM_VERSION );
 439          } else {
 440              $this->showMessage( 'config-env-php', PHP_VERSION );
 441          }
 442  
 443          $good = true;
 444          // Must go here because an old version of PCRE can prevent other checks from completing
 445          list( $pcreVersion ) = explode( ' ', PCRE_VERSION, 2 );
 446          if ( version_compare( $pcreVersion, self::MINIMUM_PCRE_VERSION, '<' ) ) {
 447              $this->showError( 'config-pcre-old', self::MINIMUM_PCRE_VERSION, $pcreVersion );
 448              $good = false;
 449          } else {
 450              foreach ( $this->envChecks as $check ) {
 451                  $status = $this->$check();
 452                  if ( $status === false ) {
 453                      $good = false;
 454                  }
 455              }
 456          }
 457  
 458          $this->setVar( '_Environment', $good );
 459  
 460          return $good ? Status::newGood() : Status::newFatal( 'config-env-bad' );
 461      }
 462  
 463  	public function doEnvironmentPreps() {
 464          foreach ( $this->envPreps as $prep ) {
 465              $this->$prep();
 466          }
 467      }
 468  
 469      /**
 470       * Set a MW configuration variable, or internal installer configuration variable.
 471       *
 472       * @param string $name
 473       * @param mixed $value
 474       */
 475  	public function setVar( $name, $value ) {
 476          $this->settings[$name] = $value;
 477      }
 478  
 479      /**
 480       * Get an MW configuration variable, or internal installer configuration variable.
 481       * The defaults come from $GLOBALS (ultimately DefaultSettings.php).
 482       * Installer variables are typically prefixed by an underscore.
 483       *
 484       * @param string $name
 485       * @param mixed $default
 486       *
 487       * @return mixed
 488       */
 489  	public function getVar( $name, $default = null ) {
 490          if ( !isset( $this->settings[$name] ) ) {
 491              return $default;
 492          } else {
 493              return $this->settings[$name];
 494          }
 495      }
 496  
 497      /**
 498       * Get a list of DBs supported by current PHP setup
 499       *
 500       * @return array
 501       */
 502  	public function getCompiledDBs() {
 503          return $this->compiledDBs;
 504      }
 505  
 506      /**
 507       * Get an instance of DatabaseInstaller for the specified DB type.
 508       *
 509       * @param mixed $type DB installer for which is needed, false to use default.
 510       *
 511       * @return DatabaseInstaller
 512       */
 513  	public function getDBInstaller( $type = false ) {
 514          if ( !$type ) {
 515              $type = $this->getVar( 'wgDBtype' );
 516          }
 517  
 518          $type = strtolower( $type );
 519  
 520          if ( !isset( $this->dbInstallers[$type] ) ) {
 521              $class = ucfirst( $type ) . 'Installer';
 522              $this->dbInstallers[$type] = new $class( $this );
 523          }
 524  
 525          return $this->dbInstallers[$type];
 526      }
 527  
 528      /**
 529       * Determine if LocalSettings.php exists. If it does, return its variables.
 530       *
 531       * @return array
 532       */
 533  	public static function getExistingLocalSettings() {
 534          global $IP;
 535  
 536          // You might be wondering why this is here. Well if you don't do this
 537          // then some poorly-formed extensions try to call their own classes
 538          // after immediately registering them. We really need to get extension
 539          // registration out of the global scope and into a real format.
 540          // @see https://bugzilla.wikimedia.org/67440
 541          global $wgAutoloadClasses;
 542          $wgAutoloadClasses = array();
 543  
 544          wfSuppressWarnings();
 545          $_lsExists = file_exists( "$IP/LocalSettings.php" );
 546          wfRestoreWarnings();
 547  
 548          if ( !$_lsExists ) {
 549              return false;
 550          }
 551          unset( $_lsExists );
 552  
 553          require "$IP/includes/DefaultSettings.php";
 554          require "$IP/LocalSettings.php";
 555  
 556          return get_defined_vars();
 557      }
 558  
 559      /**
 560       * Get a fake password for sending back to the user in HTML.
 561       * This is a security mechanism to avoid compromise of the password in the
 562       * event of session ID compromise.
 563       *
 564       * @param string $realPassword
 565       *
 566       * @return string
 567       */
 568  	public function getFakePassword( $realPassword ) {
 569          return str_repeat( '*', strlen( $realPassword ) );
 570      }
 571  
 572      /**
 573       * Set a variable which stores a password, except if the new value is a
 574       * fake password in which case leave it as it is.
 575       *
 576       * @param string $name
 577       * @param mixed $value
 578       */
 579  	public function setPassword( $name, $value ) {
 580          if ( !preg_match( '/^\*+$/', $value ) ) {
 581              $this->setVar( $name, $value );
 582          }
 583      }
 584  
 585      /**
 586       * On POSIX systems return the primary group of the webserver we're running under.
 587       * On other systems just returns null.
 588       *
 589       * This is used to advice the user that he should chgrp his mw-config/data/images directory as the
 590       * webserver user before he can install.
 591       *
 592       * Public because SqliteInstaller needs it, and doesn't subclass Installer.
 593       *
 594       * @return mixed
 595       */
 596  	public static function maybeGetWebserverPrimaryGroup() {
 597          if ( !function_exists( 'posix_getegid' ) || !function_exists( 'posix_getpwuid' ) ) {
 598              # I don't know this, this isn't UNIX.
 599              return null;
 600          }
 601  
 602          # posix_getegid() *not* getmygid() because we want the group of the webserver,
 603          # not whoever owns the current script.
 604          $gid = posix_getegid();
 605          $getpwuid = posix_getpwuid( $gid );
 606          $group = $getpwuid['name'];
 607  
 608          return $group;
 609      }
 610  
 611      /**
 612       * Convert wikitext $text to HTML.
 613       *
 614       * This is potentially error prone since many parser features require a complete
 615       * installed MW database. The solution is to just not use those features when you
 616       * write your messages. This appears to work well enough. Basic formatting and
 617       * external links work just fine.
 618       *
 619       * But in case a translator decides to throw in a "#ifexist" or internal link or
 620       * whatever, this function is guarded to catch the attempted DB access and to present
 621       * some fallback text.
 622       *
 623       * @param string $text
 624       * @param bool $lineStart
 625       * @return string
 626       */
 627  	public function parse( $text, $lineStart = false ) {
 628          global $wgParser;
 629  
 630          try {
 631              $out = $wgParser->parse( $text, $this->parserTitle, $this->parserOptions, $lineStart );
 632              $html = $out->getText();
 633          } catch ( DBAccessError $e ) {
 634              $html = '<!--DB access attempted during parse-->  ' . htmlspecialchars( $text );
 635  
 636              if ( !empty( $this->debug ) ) {
 637                  $html .= "<!--\n" . $e->getTraceAsString() . "\n-->";
 638              }
 639          }
 640  
 641          return $html;
 642      }
 643  
 644      /**
 645       * @return ParserOptions
 646       */
 647  	public function getParserOptions() {
 648          return $this->parserOptions;
 649      }
 650  
 651  	public function disableLinkPopups() {
 652          $this->parserOptions->setExternalLinkTarget( false );
 653      }
 654  
 655  	public function restoreLinkPopups() {
 656          global $wgExternalLinkTarget;
 657          $this->parserOptions->setExternalLinkTarget( $wgExternalLinkTarget );
 658      }
 659  
 660      /**
 661       * Install step which adds a row to the site_stats table with appropriate
 662       * initial values.
 663       *
 664       * @param DatabaseInstaller $installer
 665       *
 666       * @return Status
 667       */
 668  	public function populateSiteStats( DatabaseInstaller $installer ) {
 669          $status = $installer->getConnection();
 670          if ( !$status->isOK() ) {
 671              return $status;
 672          }
 673          $status->value->insert(
 674              'site_stats',
 675              array(
 676                  'ss_row_id' => 1,
 677                  'ss_total_views' => 0,
 678                  'ss_total_edits' => 0,
 679                  'ss_good_articles' => 0,
 680                  'ss_total_pages' => 0,
 681                  'ss_users' => 0,
 682                  'ss_images' => 0
 683              ),
 684              __METHOD__, 'IGNORE'
 685          );
 686  
 687          return Status::newGood();
 688      }
 689  
 690      /**
 691       * Exports all wg* variables stored by the installer into global scope.
 692       */
 693  	public function exportVars() {
 694          foreach ( $this->settings as $name => $value ) {
 695              if ( substr( $name, 0, 2 ) == 'wg' ) {
 696                  $GLOBALS[$name] = $value;
 697              }
 698          }
 699      }
 700  
 701      /**
 702       * Environment check for DB types.
 703       * @return bool
 704       */
 705  	protected function envCheckDB() {
 706          global $wgLang;
 707  
 708          $allNames = array();
 709  
 710          // Messages: config-type-mysql, config-type-postgres, config-type-oracle,
 711          // config-type-sqlite
 712          foreach ( self::getDBTypes() as $name ) {
 713              $allNames[] = wfMessage( "config-type-$name" )->text();
 714          }
 715  
 716          $databases = $this->getCompiledDBs();
 717  
 718          $databases = array_flip( $databases );
 719          foreach ( array_keys( $databases ) as $db ) {
 720              $installer = $this->getDBInstaller( $db );
 721              $status = $installer->checkPrerequisites();
 722              if ( !$status->isGood() ) {
 723                  $this->showStatusMessage( $status );
 724              }
 725              if ( !$status->isOK() ) {
 726                  unset( $databases[$db] );
 727              }
 728          }
 729          $databases = array_flip( $databases );
 730          if ( !$databases ) {
 731              $this->showError( 'config-no-db', $wgLang->commaList( $allNames ) );
 732  
 733              // @todo FIXME: This only works for the web installer!
 734              return false;
 735          }
 736  
 737          return true;
 738      }
 739  
 740      /**
 741       * Environment check for register_globals.
 742       * Prevent installation if enabled
 743       * @return bool
 744       */
 745  	protected function envCheckRegisterGlobals() {
 746          if ( wfIniGetBool( 'register_globals' ) ) {
 747              $this->showMessage( 'config-register-globals-error' );
 748              return false;
 749          }
 750  
 751          return true;
 752      }
 753  
 754      /**
 755       * Some versions of libxml+PHP break < and > encoding horribly
 756       * @return bool
 757       */
 758  	protected function envCheckBrokenXML() {
 759          $test = new PhpXmlBugTester();
 760          if ( !$test->ok ) {
 761              $this->showError( 'config-brokenlibxml' );
 762  
 763              return false;
 764          }
 765  
 766          return true;
 767      }
 768  
 769      /**
 770       * Environment check for magic_quotes_(gpc|runtime|sybase).
 771       * @return bool
 772       */
 773  	protected function envCheckMagicQuotes() {
 774          $status = true;
 775          foreach ( array( 'gpc', 'runtime', 'sybase' ) as $magicJunk ) {
 776              if ( wfIniGetBool( "magic_quotes_$magicJunk" ) ) {
 777                  $this->showError( "config-magic-quotes-$magicJunk" );
 778                  $status = false;
 779              }
 780          }
 781  
 782          return $status;
 783      }
 784  
 785      /**
 786       * Environment check for mbstring.func_overload.
 787       * @return bool
 788       */
 789  	protected function envCheckMbstring() {
 790          if ( wfIniGetBool( 'mbstring.func_overload' ) ) {
 791              $this->showError( 'config-mbstring' );
 792  
 793              return false;
 794          }
 795  
 796          return true;
 797      }
 798  
 799      /**
 800       * Environment check for safe_mode.
 801       * @return bool
 802       */
 803  	protected function envCheckSafeMode() {
 804          if ( wfIniGetBool( 'safe_mode' ) ) {
 805              $this->setVar( '_SafeMode', true );
 806              $this->showMessage( 'config-safe-mode' );
 807          }
 808  
 809          return true;
 810      }
 811  
 812      /**
 813       * Environment check for the XML module.
 814       * @return bool
 815       */
 816  	protected function envCheckXML() {
 817          if ( !function_exists( "utf8_encode" ) ) {
 818              $this->showError( 'config-xml-bad' );
 819  
 820              return false;
 821          }
 822  
 823          return true;
 824      }
 825  
 826      /**
 827       * Environment check for the PCRE module.
 828       *
 829       * @note If this check were to fail, the parser would
 830       *   probably throw an exception before the result
 831       *   of this check is shown to the user.
 832       * @return bool
 833       */
 834  	protected function envCheckPCRE() {
 835          wfSuppressWarnings();
 836          $regexd = preg_replace( '/[\x{0430}-\x{04FF}]/iu', '', '-АБВГД-' );
 837          // Need to check for \p support too, as PCRE can be compiled
 838          // with utf8 support, but not unicode property support.
 839          // check that \p{Zs} (space separators) matches
 840          // U+3000 (Ideographic space)
 841          $regexprop = preg_replace( '/\p{Zs}/u', '', "-\xE3\x80\x80-" );
 842          wfRestoreWarnings();
 843          if ( $regexd != '--' || $regexprop != '--' ) {
 844              $this->showError( 'config-pcre-no-utf8' );
 845  
 846              return false;
 847          }
 848  
 849          return true;
 850      }
 851  
 852      /**
 853       * Environment check for available memory.
 854       * @return bool
 855       */
 856  	protected function envCheckMemory() {
 857          $limit = ini_get( 'memory_limit' );
 858  
 859          if ( !$limit || $limit == -1 ) {
 860              return true;
 861          }
 862  
 863          $n = wfShorthandToInteger( $limit );
 864  
 865          if ( $n < $this->minMemorySize * 1024 * 1024 ) {
 866              $newLimit = "{$this->minMemorySize}M";
 867  
 868              if ( ini_set( "memory_limit", $newLimit ) === false ) {
 869                  $this->showMessage( 'config-memory-bad', $limit );
 870              } else {
 871                  $this->showMessage( 'config-memory-raised', $limit, $newLimit );
 872                  $this->setVar( '_RaiseMemory', true );
 873              }
 874          }
 875  
 876          return true;
 877      }
 878  
 879      /**
 880       * Environment check for compiled object cache types.
 881       */
 882  	protected function envCheckCache() {
 883          $caches = array();
 884          foreach ( $this->objectCaches as $name => $function ) {
 885              if ( function_exists( $function ) ) {
 886                  if ( $name == 'xcache' && !wfIniGetBool( 'xcache.var_size' ) ) {
 887                      continue;
 888                  }
 889                  $caches[$name] = true;
 890              }
 891          }
 892  
 893          if ( !$caches ) {
 894              $this->showMessage( 'config-no-cache' );
 895          }
 896  
 897          $this->setVar( '_Caches', $caches );
 898      }
 899  
 900      /**
 901       * Scare user to death if they have mod_security
 902       * @return bool
 903       */
 904  	protected function envCheckModSecurity() {
 905          if ( self::apacheModulePresent( 'mod_security' ) ) {
 906              $this->showMessage( 'config-mod-security' );
 907          }
 908  
 909          return true;
 910      }
 911  
 912      /**
 913       * Search for GNU diff3.
 914       * @return bool
 915       */
 916  	protected function envCheckDiff3() {
 917          $names = array( "gdiff3", "diff3", "diff3.exe" );
 918          $versionInfo = array( '$1 --version 2>&1', 'GNU diffutils' );
 919  
 920          $diff3 = self::locateExecutableInDefaultPaths( $names, $versionInfo );
 921  
 922          if ( $diff3 ) {
 923              $this->setVar( 'wgDiff3', $diff3 );
 924          } else {
 925              $this->setVar( 'wgDiff3', false );
 926              $this->showMessage( 'config-diff3-bad' );
 927          }
 928  
 929          return true;
 930      }
 931  
 932      /**
 933       * Environment check for ImageMagick and GD.
 934       * @return bool
 935       */
 936  	protected function envCheckGraphics() {
 937          $names = array( wfIsWindows() ? 'convert.exe' : 'convert' );
 938          $versionInfo = array( '$1 -version', 'ImageMagick' );
 939          $convert = self::locateExecutableInDefaultPaths( $names, $versionInfo );
 940  
 941          $this->setVar( 'wgImageMagickConvertCommand', '' );
 942          if ( $convert ) {
 943              $this->setVar( 'wgImageMagickConvertCommand', $convert );
 944              $this->showMessage( 'config-imagemagick', $convert );
 945  
 946              return true;
 947          } elseif ( function_exists( 'imagejpeg' ) ) {
 948              $this->showMessage( 'config-gd' );
 949          } else {
 950              $this->showMessage( 'config-no-scaling' );
 951          }
 952  
 953          return true;
 954      }
 955  
 956      /**
 957       * Search for git.
 958       *
 959       * @since 1.22
 960       * @return bool
 961       */
 962  	protected function envCheckGit() {
 963          $names = array( wfIsWindows() ? 'git.exe' : 'git' );
 964          $versionInfo = array( '$1 --version', 'git version' );
 965  
 966          $git = self::locateExecutableInDefaultPaths( $names, $versionInfo );
 967  
 968          if ( $git ) {
 969              $this->setVar( 'wgGitBin', $git );
 970              $this->showMessage( 'config-git', $git );
 971          } else {
 972              $this->setVar( 'wgGitBin', false );
 973              $this->showMessage( 'config-git-bad' );
 974          }
 975  
 976          return true;
 977      }
 978  
 979      /**
 980       * Environment check to inform user which server we've assumed.
 981       *
 982       * @return bool
 983       */
 984  	protected function envCheckServer() {
 985          $server = $this->envGetDefaultServer();
 986          if ( $server !== null ) {
 987              $this->showMessage( 'config-using-server', $server );
 988          }
 989          return true;
 990      }
 991  
 992      /**
 993       * Environment check to inform user which paths we've assumed.
 994       *
 995       * @return bool
 996       */
 997  	protected function envCheckPath() {
 998          $this->showMessage(
 999              'config-using-uri',
1000              $this->getVar( 'wgServer' ),
1001              $this->getVar( 'wgScriptPath' )
1002          );
1003          return true;
1004      }
1005  
1006      /**
1007       * Environment check for preferred locale in shell
1008       * @return bool
1009       */
1010  	protected function envCheckShellLocale() {
1011          $os = php_uname( 's' );
1012          $supported = array( 'Linux', 'SunOS', 'HP-UX', 'Darwin' ); # Tested these
1013  
1014          if ( !in_array( $os, $supported ) ) {
1015              return true;
1016          }
1017  
1018          # Get a list of available locales.
1019          $ret = false;
1020          $lines = wfShellExec( '/usr/bin/locale -a', $ret );
1021  
1022          if ( $ret ) {
1023              return true;
1024          }
1025  
1026          $lines = array_map( 'trim', explode( "\n", $lines ) );
1027          $candidatesByLocale = array();
1028          $candidatesByLang = array();
1029  
1030          foreach ( $lines as $line ) {
1031              if ( $line === '' ) {
1032                  continue;
1033              }
1034  
1035              if ( !preg_match( '/^([a-zA-Z]+)(_[a-zA-Z]+|)\.(utf8|UTF-8)(@[a-zA-Z_]*|)$/i', $line, $m ) ) {
1036                  continue;
1037              }
1038  
1039              list( , $lang, , , ) = $m;
1040  
1041              $candidatesByLocale[$m[0]] = $m;
1042              $candidatesByLang[$lang][] = $m;
1043          }
1044  
1045          # Try the current value of LANG.
1046          if ( isset( $candidatesByLocale[getenv( 'LANG' )] ) ) {
1047              $this->setVar( 'wgShellLocale', getenv( 'LANG' ) );
1048  
1049              return true;
1050          }
1051  
1052          # Try the most common ones.
1053          $commonLocales = array( 'en_US.UTF-8', 'en_US.utf8', 'de_DE.UTF-8', 'de_DE.utf8' );
1054          foreach ( $commonLocales as $commonLocale ) {
1055              if ( isset( $candidatesByLocale[$commonLocale] ) ) {
1056                  $this->setVar( 'wgShellLocale', $commonLocale );
1057  
1058                  return true;
1059              }
1060          }
1061  
1062          # Is there an available locale in the Wiki's language?
1063          $wikiLang = $this->getVar( 'wgLanguageCode' );
1064  
1065          if ( isset( $candidatesByLang[$wikiLang] ) ) {
1066              $m = reset( $candidatesByLang[$wikiLang] );
1067              $this->setVar( 'wgShellLocale', $m[0] );
1068  
1069              return true;
1070          }
1071  
1072          # Are there any at all?
1073          if ( count( $candidatesByLocale ) ) {
1074              $m = reset( $candidatesByLocale );
1075              $this->setVar( 'wgShellLocale', $m[0] );
1076  
1077              return true;
1078          }
1079  
1080          # Give up.
1081          return true;
1082      }
1083  
1084      /**
1085       * Environment check for the permissions of the uploads directory
1086       * @return bool
1087       */
1088  	protected function envCheckUploadsDirectory() {
1089          global $IP;
1090  
1091          $dir = $IP . '/images/';
1092          $url = $this->getVar( 'wgServer' ) . $this->getVar( 'wgScriptPath' ) . '/images/';
1093          $safe = !$this->dirIsExecutable( $dir, $url );
1094  
1095          if ( !$safe ) {
1096              $this->showMessage( 'config-uploads-not-safe', $dir );
1097          }
1098  
1099          return true;
1100      }
1101  
1102      /**
1103       * Checks if suhosin.get.max_value_length is set, and if so generate
1104       * a warning because it decreases ResourceLoader performance.
1105       * @return bool
1106       */
1107  	protected function envCheckSuhosinMaxValueLength() {
1108          $maxValueLength = ini_get( 'suhosin.get.max_value_length' );
1109          if ( $maxValueLength > 0 && $maxValueLength < 1024 ) {
1110              // Only warn if the value is below the sane 1024
1111              $this->showMessage( 'config-suhosin-max-value-length', $maxValueLength );
1112          }
1113  
1114          return true;
1115      }
1116  
1117      /**
1118       * Convert a hex string representing a Unicode code point to that code point.
1119       * @param string $c
1120       * @return string
1121       */
1122  	protected function unicodeChar( $c ) {
1123          $c = hexdec( $c );
1124          if ( $c <= 0x7F ) {
1125              return chr( $c );
1126          } elseif ( $c <= 0x7FF ) {
1127              return chr( 0xC0 | $c >> 6 ) . chr( 0x80 | $c & 0x3F );
1128          } elseif ( $c <= 0xFFFF ) {
1129              return chr( 0xE0 | $c >> 12 ) . chr( 0x80 | $c >> 6 & 0x3F )
1130              . chr( 0x80 | $c & 0x3F );
1131          } elseif ( $c <= 0x10FFFF ) {
1132              return chr( 0xF0 | $c >> 18 ) . chr( 0x80 | $c >> 12 & 0x3F )
1133              . chr( 0x80 | $c >> 6 & 0x3F )
1134              . chr( 0x80 | $c & 0x3F );
1135          } else {
1136              return false;
1137          }
1138      }
1139  
1140      /**
1141       * Check the libicu version
1142       */
1143  	protected function envCheckLibicu() {
1144          $utf8 = function_exists( 'utf8_normalize' );
1145          $intl = function_exists( 'normalizer_normalize' );
1146  
1147          /**
1148           * This needs to be updated something that the latest libicu
1149           * will properly normalize.  This normalization was found at
1150           * http://www.unicode.org/versions/Unicode5.2.0/#Character_Additions
1151           * Note that we use the hex representation to create the code
1152           * points in order to avoid any Unicode-destroying during transit.
1153           */
1154          $not_normal_c = $this->unicodeChar( "FA6C" );
1155          $normal_c = $this->unicodeChar( "242EE" );
1156  
1157          $useNormalizer = 'php';
1158          $needsUpdate = false;
1159  
1160          /**
1161           * We're going to prefer the pecl extension here unless
1162           * utf8_normalize is more up to date.
1163           */
1164          if ( $utf8 ) {
1165              $useNormalizer = 'utf8';
1166              $utf8 = utf8_normalize( $not_normal_c, UtfNormal::UNORM_NFC );
1167              if ( $utf8 !== $normal_c ) {
1168                  $needsUpdate = true;
1169              }
1170          }
1171          if ( $intl ) {
1172              $useNormalizer = 'intl';
1173              $intl = normalizer_normalize( $not_normal_c, Normalizer::FORM_C );
1174              if ( $intl !== $normal_c ) {
1175                  $needsUpdate = true;
1176              }
1177          }
1178  
1179          // Uses messages 'config-unicode-using-php', 'config-unicode-using-utf8',
1180          // 'config-unicode-using-intl'
1181          if ( $useNormalizer === 'php' ) {
1182              $this->showMessage( 'config-unicode-pure-php-warning' );
1183          } else {
1184              $this->showMessage( 'config-unicode-using-' . $useNormalizer );
1185              if ( $needsUpdate ) {
1186                  $this->showMessage( 'config-unicode-update-warning' );
1187              }
1188          }
1189      }
1190  
1191      /**
1192       * @return bool
1193       */
1194  	protected function envCheckCtype() {
1195          if ( !function_exists( 'ctype_digit' ) ) {
1196              $this->showError( 'config-ctype' );
1197  
1198              return false;
1199          }
1200  
1201          return true;
1202      }
1203  
1204      /**
1205       * @return bool
1206       */
1207  	protected function envCheckIconv() {
1208          if ( !function_exists( 'iconv' ) ) {
1209              $this->showError( 'config-iconv' );
1210  
1211              return false;
1212          }
1213  
1214          return true;
1215      }
1216  
1217      /**
1218       * @return bool
1219       */
1220  	protected function envCheckJSON() {
1221          if ( !function_exists( 'json_decode' ) ) {
1222              $this->showError( 'config-json' );
1223  
1224              return false;
1225          }
1226  
1227          return true;
1228      }
1229  
1230      /**
1231       * Environment prep for the server hostname.
1232       */
1233  	protected function envPrepServer() {
1234          $server = $this->envGetDefaultServer();
1235          if ( $server !== null ) {
1236              $this->setVar( 'wgServer', $server );
1237          }
1238      }
1239  
1240      /**
1241       * Helper function to be called from envPrepServer()
1242       * @return string
1243       */
1244      abstract protected function envGetDefaultServer();
1245  
1246      /**
1247       * Environment prep for setting the preferred PHP file extension.
1248       */
1249  	protected function envPrepExtension() {
1250          // @todo FIXME: Detect this properly
1251          if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) {
1252              $ext = '.php5';
1253          } else {
1254              $ext = '.php';
1255          }
1256          $this->setVar( 'wgScriptExtension', $ext );
1257      }
1258  
1259      /**
1260       * Environment prep for setting $IP and $wgScriptPath.
1261       */
1262  	protected function envPrepPath() {
1263          global $IP;
1264          $IP = dirname( dirname( __DIR__ ) );
1265          $this->setVar( 'IP', $IP );
1266      }
1267  
1268      /**
1269       * Get an array of likely places we can find executables. Check a bunch
1270       * of known Unix-like defaults, as well as the PATH environment variable
1271       * (which should maybe make it work for Windows?)
1272       *
1273       * @return array
1274       */
1275  	protected static function getPossibleBinPaths() {
1276          return array_merge(
1277              array( '/usr/bin', '/usr/local/bin', '/opt/csw/bin',
1278                  '/usr/gnu/bin', '/usr/sfw/bin', '/sw/bin', '/opt/local/bin' ),
1279              explode( PATH_SEPARATOR, getenv( 'PATH' ) )
1280          );
1281      }
1282  
1283      /**
1284       * Search a path for any of the given executable names. Returns the
1285       * executable name if found. Also checks the version string returned
1286       * by each executable.
1287       *
1288       * Used only by environment checks.
1289       *
1290       * @param string $path Path to search
1291       * @param array $names Array of executable names
1292       * @param array|bool $versionInfo False or array with two members:
1293       *   0 => Command to run for version check, with $1 for the full executable name
1294       *   1 => String to compare the output with
1295       *
1296       * If $versionInfo is not false, only executables with a version
1297       * matching $versionInfo[1] will be returned.
1298       * @return bool|string
1299       */
1300  	public static function locateExecutable( $path, $names, $versionInfo = false ) {
1301          if ( !is_array( $names ) ) {
1302              $names = array( $names );
1303          }
1304  
1305          foreach ( $names as $name ) {
1306              $command = $path . DIRECTORY_SEPARATOR . $name;
1307  
1308              wfSuppressWarnings();
1309              $file_exists = file_exists( $command );
1310              wfRestoreWarnings();
1311  
1312              if ( $file_exists ) {
1313                  if ( !$versionInfo ) {
1314                      return $command;
1315                  }
1316  
1317                  $file = str_replace( '$1', wfEscapeShellArg( $command ), $versionInfo[0] );
1318                  if ( strstr( wfShellExec( $file ), $versionInfo[1] ) !== false ) {
1319                      return $command;
1320                  }
1321              }
1322          }
1323  
1324          return false;
1325      }
1326  
1327      /**
1328       * Same as locateExecutable(), but checks in getPossibleBinPaths() by default
1329       * @see locateExecutable()
1330       * @param array $names Array of possible names.
1331       * @param array|bool $versionInfo Default: false or array with two members:
1332       *   0 => Command to run for version check, with $1 for the full executable name
1333       *   1 => String to compare the output with
1334       *
1335       * If $versionInfo is not false, only executables with a version
1336       * matching $versionInfo[1] will be returned.
1337       * @return bool|string
1338       */
1339  	public static function locateExecutableInDefaultPaths( $names, $versionInfo = false ) {
1340          foreach ( self::getPossibleBinPaths() as $path ) {
1341              $exe = self::locateExecutable( $path, $names, $versionInfo );
1342              if ( $exe !== false ) {
1343                  return $exe;
1344              }
1345          }
1346  
1347          return false;
1348      }
1349  
1350      /**
1351       * Checks if scripts located in the given directory can be executed via the given URL.
1352       *
1353       * Used only by environment checks.
1354       * @param string $dir
1355       * @param string $url
1356       * @return bool|int|string
1357       */
1358  	public function dirIsExecutable( $dir, $url ) {
1359          $scriptTypes = array(
1360              'php' => array(
1361                  "<?php echo 'ex' . 'ec';",
1362                  "#!/var/env php5\n<?php echo 'ex' . 'ec';",
1363              ),
1364          );
1365  
1366          // it would be good to check other popular languages here, but it'll be slow.
1367  
1368          wfSuppressWarnings();
1369  
1370          foreach ( $scriptTypes as $ext => $contents ) {
1371              foreach ( $contents as $source ) {
1372                  $file = 'exectest.' . $ext;
1373  
1374                  if ( !file_put_contents( $dir . $file, $source ) ) {
1375                      break;
1376                  }
1377  
1378                  try {
1379                      $text = Http::get( $url . $file, array( 'timeout' => 3 ) );
1380                  } catch ( MWException $e ) {
1381                      // Http::get throws with allow_url_fopen = false and no curl extension.
1382                      $text = null;
1383                  }
1384                  unlink( $dir . $file );
1385  
1386                  if ( $text == 'exec' ) {
1387                      wfRestoreWarnings();
1388  
1389                      return $ext;
1390                  }
1391              }
1392          }
1393  
1394          wfRestoreWarnings();
1395  
1396          return false;
1397      }
1398  
1399      /**
1400       * Checks for presence of an Apache module. Works only if PHP is running as an Apache module, too.
1401       *
1402       * @param string $moduleName Name of module to check.
1403       * @return bool
1404       */
1405  	public static function apacheModulePresent( $moduleName ) {
1406          if ( function_exists( 'apache_get_modules' ) && in_array( $moduleName, apache_get_modules() ) ) {
1407              return true;
1408          }
1409          // try it the hard way
1410          ob_start();
1411          phpinfo( INFO_MODULES );
1412          $info = ob_get_clean();
1413  
1414          return strpos( $info, $moduleName ) !== false;
1415      }
1416  
1417      /**
1418       * ParserOptions are constructed before we determined the language, so fix it
1419       *
1420       * @param Language $lang
1421       */
1422  	public function setParserLanguage( $lang ) {
1423          $this->parserOptions->setTargetLanguage( $lang );
1424          $this->parserOptions->setUserLang( $lang );
1425      }
1426  
1427      /**
1428       * Overridden by WebInstaller to provide lastPage parameters.
1429       * @param string $page
1430       * @return string
1431       */
1432  	protected function getDocUrl( $page ) {
1433          return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page );
1434      }
1435  
1436      /**
1437       * Finds extensions that follow the format /$directory/Name/Name.php,
1438       * and returns an array containing the value for 'Name' for each found extension.
1439       *
1440       * Reasonable values for $directory include 'extensions' (the default) and 'skins'.
1441       *
1442       * @param string $directory Directory to search in
1443       * @return array
1444       */
1445  	public function findExtensions( $directory = 'extensions' ) {
1446          if ( $this->getVar( 'IP' ) === null ) {
1447              return array();
1448          }
1449  
1450          $extDir = $this->getVar( 'IP' ) . '/' . $directory;
1451          if ( !is_readable( $extDir ) || !is_dir( $extDir ) ) {
1452              return array();
1453          }
1454  
1455          $dh = opendir( $extDir );
1456          $exts = array();
1457          while ( ( $file = readdir( $dh ) ) !== false ) {
1458              if ( !is_dir( "$extDir/$file" ) ) {
1459                  continue;
1460              }
1461              if ( file_exists( "$extDir/$file/$file.php" ) ) {
1462                  $exts[] = $file;
1463              }
1464          }
1465          closedir( $dh );
1466          natcasesort( $exts );
1467  
1468          return $exts;
1469      }
1470  
1471      /**
1472       * Returns a default value to be used for $wgDefaultSkin: the preferred skin, if available among
1473       * the installed skins, or any other one otherwise.
1474       *
1475       * @param string[] $skinNames Names of installed skins.
1476       * @return string
1477       */
1478  	public function getDefaultSkin( array $skinNames ) {
1479          $defaultSkin = $GLOBALS['wgDefaultSkin'];
1480          if ( in_array( $defaultSkin, $skinNames ) ) {
1481              return $defaultSkin;
1482          } else {
1483              return $skinNames[0];
1484          }
1485      }
1486  
1487      /**
1488       * Installs the auto-detected extensions.
1489       *
1490       * @return Status
1491       */
1492  	protected function includeExtensions() {
1493          global $IP;
1494          $exts = $this->getVar( '_Extensions' );
1495          $IP = $this->getVar( 'IP' );
1496  
1497          /**
1498           * We need to include DefaultSettings before including extensions to avoid
1499           * warnings about unset variables. However, the only thing we really
1500           * want here is $wgHooks['LoadExtensionSchemaUpdates']. This won't work
1501           * if the extension has hidden hook registration in $wgExtensionFunctions,
1502           * but we're not opening that can of worms
1503           * @see https://bugzilla.wikimedia.org/show_bug.cgi?id=26857
1504           */
1505          global $wgAutoloadClasses;
1506          $wgAutoloadClasses = array();
1507  
1508          require "$IP/includes/DefaultSettings.php";
1509  
1510          foreach ( $exts as $e ) {
1511              require_once "$IP/extensions/$e/$e.php";
1512          }
1513  
1514          $hooksWeWant = isset( $wgHooks['LoadExtensionSchemaUpdates'] ) ?
1515              $wgHooks['LoadExtensionSchemaUpdates'] : array();
1516  
1517          // Unset everyone else's hooks. Lord knows what someone might be doing
1518          // in ParserFirstCallInit (see bug 27171)
1519          $GLOBALS['wgHooks'] = array( 'LoadExtensionSchemaUpdates' => $hooksWeWant );
1520  
1521          return Status::newGood();
1522      }
1523  
1524      /**
1525       * Get an array of install steps. Should always be in the format of
1526       * array(
1527       *   'name'     => 'someuniquename',
1528       *   'callback' => array( $obj, 'method' ),
1529       * )
1530       * There must be a config-install-$name message defined per step, which will
1531       * be shown on install.
1532       *
1533       * @param DatabaseInstaller $installer DatabaseInstaller so we can make callbacks
1534       * @return array
1535       */
1536  	protected function getInstallSteps( DatabaseInstaller $installer ) {
1537          $coreInstallSteps = array(
1538              array( 'name' => 'database', 'callback' => array( $installer, 'setupDatabase' ) ),
1539              array( 'name' => 'tables', 'callback' => array( $installer, 'createTables' ) ),
1540              array( 'name' => 'interwiki', 'callback' => array( $installer, 'populateInterwikiTable' ) ),
1541              array( 'name' => 'stats', 'callback' => array( $this, 'populateSiteStats' ) ),
1542              array( 'name' => 'keys', 'callback' => array( $this, 'generateKeys' ) ),
1543              array( 'name' => 'updates', 'callback' => array( $installer, 'insertUpdateKeys' ) ),
1544              array( 'name' => 'sysop', 'callback' => array( $this, 'createSysop' ) ),
1545              array( 'name' => 'mainpage', 'callback' => array( $this, 'createMainpage' ) ),
1546          );
1547  
1548          // Build the array of install steps starting from the core install list,
1549          // then adding any callbacks that wanted to attach after a given step
1550          foreach ( $coreInstallSteps as $step ) {
1551              $this->installSteps[] = $step;
1552              if ( isset( $this->extraInstallSteps[$step['name']] ) ) {
1553                  $this->installSteps = array_merge(
1554                      $this->installSteps,
1555                      $this->extraInstallSteps[$step['name']]
1556                  );
1557              }
1558          }
1559  
1560          // Prepend any steps that want to be at the beginning
1561          if ( isset( $this->extraInstallSteps['BEGINNING'] ) ) {
1562              $this->installSteps = array_merge(
1563                  $this->extraInstallSteps['BEGINNING'],
1564                  $this->installSteps
1565              );
1566          }
1567  
1568          // Extensions should always go first, chance to tie into hooks and such
1569          if ( count( $this->getVar( '_Extensions' ) ) ) {
1570              array_unshift( $this->installSteps,
1571                  array( 'name' => 'extensions', 'callback' => array( $this, 'includeExtensions' ) )
1572              );
1573              $this->installSteps[] = array(
1574                  'name' => 'extension-tables',
1575                  'callback' => array( $installer, 'createExtensionTables' )
1576              );
1577          }
1578  
1579          return $this->installSteps;
1580      }
1581  
1582      /**
1583       * Actually perform the installation.
1584       *
1585       * @param callable $startCB A callback array for the beginning of each step
1586       * @param callable $endCB A callback array for the end of each step
1587       *
1588       * @return array Array of Status objects
1589       */
1590  	public function performInstallation( $startCB, $endCB ) {
1591          $installResults = array();
1592          $installer = $this->getDBInstaller();
1593          $installer->preInstall();
1594          $steps = $this->getInstallSteps( $installer );
1595          foreach ( $steps as $stepObj ) {
1596              $name = $stepObj['name'];
1597              call_user_func_array( $startCB, array( $name ) );
1598  
1599              // Perform the callback step
1600              $status = call_user_func( $stepObj['callback'], $installer );
1601  
1602              // Output and save the results
1603              call_user_func( $endCB, $name, $status );
1604              $installResults[$name] = $status;
1605  
1606              // If we've hit some sort of fatal, we need to bail.
1607              // Callback already had a chance to do output above.
1608              if ( !$status->isOk() ) {
1609                  break;
1610              }
1611          }
1612          if ( $status->isOk() ) {
1613              $this->setVar( '_InstallDone', true );
1614          }
1615  
1616          return $installResults;
1617      }
1618  
1619      /**
1620       * Generate $wgSecretKey. Will warn if we had to use an insecure random source.
1621       *
1622       * @return Status
1623       */
1624  	public function generateKeys() {
1625          $keys = array( 'wgSecretKey' => 64 );
1626          if ( strval( $this->getVar( 'wgUpgradeKey' ) ) === '' ) {
1627              $keys['wgUpgradeKey'] = 16;
1628          }
1629  
1630          return $this->doGenerateKeys( $keys );
1631      }
1632  
1633      /**
1634       * Generate a secret value for variables using our CryptRand generator.
1635       * Produce a warning if the random source was insecure.
1636       *
1637       * @param array $keys
1638       * @return Status
1639       */
1640  	protected function doGenerateKeys( $keys ) {
1641          $status = Status::newGood();
1642  
1643          $strong = true;
1644          foreach ( $keys as $name => $length ) {
1645              $secretKey = MWCryptRand::generateHex( $length, true );
1646              if ( !MWCryptRand::wasStrong() ) {
1647                  $strong = false;
1648              }
1649  
1650              $this->setVar( $name, $secretKey );
1651          }
1652  
1653          if ( !$strong ) {
1654              $names = array_keys( $keys );
1655              $names = preg_replace( '/^(.*)$/', '\$$1', $names );
1656              global $wgLang;
1657              $status->warning( 'config-insecure-keys', $wgLang->listToText( $names ), count( $names ) );
1658          }
1659  
1660          return $status;
1661      }
1662  
1663      /**
1664       * Create the first user account, grant it sysop and bureaucrat rights
1665       *
1666       * @return Status
1667       */
1668  	protected function createSysop() {
1669          $name = $this->getVar( '_AdminName' );
1670          $user = User::newFromName( $name );
1671  
1672          if ( !$user ) {
1673              // We should've validated this earlier anyway!
1674              return Status::newFatal( 'config-admin-error-user', $name );
1675          }
1676  
1677          if ( $user->idForName() == 0 ) {
1678              $user->addToDatabase();
1679  
1680              try {
1681                  $user->setPassword( $this->getVar( '_AdminPassword' ) );
1682              } catch ( PasswordError $pwe ) {
1683                  return Status::newFatal( 'config-admin-error-password', $name, $pwe->getMessage() );
1684              }
1685  
1686              $user->addGroup( 'sysop' );
1687              $user->addGroup( 'bureaucrat' );
1688              if ( $this->getVar( '_AdminEmail' ) ) {
1689                  $user->setEmail( $this->getVar( '_AdminEmail' ) );
1690              }
1691              $user->saveSettings();
1692  
1693              // Update user count
1694              $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 );
1695              $ssUpdate->doUpdate();
1696          }
1697          $status = Status::newGood();
1698  
1699          if ( $this->getVar( '_Subscribe' ) && $this->getVar( '_AdminEmail' ) ) {
1700              $this->subscribeToMediaWikiAnnounce( $status );
1701          }
1702  
1703          return $status;
1704      }
1705  
1706      /**
1707       * @param Status $s
1708       */
1709  	private function subscribeToMediaWikiAnnounce( Status $s ) {
1710          $params = array(
1711              'email' => $this->getVar( '_AdminEmail' ),
1712              'language' => 'en',
1713              'digest' => 0
1714          );
1715  
1716          // Mailman doesn't support as many languages as we do, so check to make
1717          // sure their selected language is available
1718          $myLang = $this->getVar( '_UserLang' );
1719          if ( in_array( $myLang, $this->mediaWikiAnnounceLanguages ) ) {
1720              $myLang = $myLang == 'pt-br' ? 'pt_BR' : $myLang; // rewrite to Mailman's pt_BR
1721              $params['language'] = $myLang;
1722          }
1723  
1724          if ( MWHttpRequest::canMakeRequests() ) {
1725              $res = MWHttpRequest::factory( $this->mediaWikiAnnounceUrl,
1726                  array( 'method' => 'POST', 'postData' => $params ) )->execute();
1727              if ( !$res->isOK() ) {
1728                  $s->warning( 'config-install-subscribe-fail', $res->getMessage() );
1729              }
1730          } else {
1731              $s->warning( 'config-install-subscribe-notpossible' );
1732          }
1733      }
1734  
1735      /**
1736       * Insert Main Page with default content.
1737       *
1738       * @param DatabaseInstaller $installer
1739       * @return Status
1740       */
1741  	protected function createMainpage( DatabaseInstaller $installer ) {
1742          $status = Status::newGood();
1743          try {
1744              $page = WikiPage::factory( Title::newMainPage() );
1745              $content = new WikitextContent(
1746                  wfMessage( 'mainpagetext' )->inContentLanguage()->text() . "\n\n" .
1747                  wfMessage( 'mainpagedocfooter' )->inContentLanguage()->text()
1748              );
1749  
1750              $page->doEditContent( $content,
1751                  '',
1752                  EDIT_NEW,
1753                  false,
1754                  User::newFromName( 'MediaWiki default' )
1755              );
1756          } catch ( MWException $e ) {
1757              //using raw, because $wgShowExceptionDetails can not be set yet
1758              $status->fatal( 'config-install-mainpage-failed', $e->getMessage() );
1759          }
1760  
1761          return $status;
1762      }
1763  
1764      /**
1765       * Override the necessary bits of the config to run an installation.
1766       */
1767  	public static function overrideConfig() {
1768          define( 'MW_NO_SESSION', 1 );
1769  
1770          // Don't access the database
1771          $GLOBALS['wgUseDatabaseMessages'] = false;
1772          // Don't cache langconv tables
1773          $GLOBALS['wgLanguageConverterCacheType'] = CACHE_NONE;
1774          // Debug-friendly
1775          $GLOBALS['wgShowExceptionDetails'] = true;
1776          // Don't break forms
1777          $GLOBALS['wgExternalLinkTarget'] = '_blank';
1778  
1779          // Extended debugging
1780          $GLOBALS['wgShowSQLErrors'] = true;
1781          $GLOBALS['wgShowDBErrorBacktrace'] = true;
1782  
1783          // Allow multiple ob_flush() calls
1784          $GLOBALS['wgDisableOutputCompression'] = true;
1785  
1786          // Use a sensible cookie prefix (not my_wiki)
1787          $GLOBALS['wgCookiePrefix'] = 'mw_installer';
1788  
1789          // Some of the environment checks make shell requests, remove limits
1790          $GLOBALS['wgMaxShellMemory'] = 0;
1791      }
1792  
1793      /**
1794       * Add an installation step following the given step.
1795       *
1796       * @param callable $callback A valid installation callback array, in this form:
1797       *    array( 'name' => 'some-unique-name', 'callback' => array( $obj, 'function' ) );
1798       * @param string $findStep The step to find. Omit to put the step at the beginning
1799       */
1800  	public function addInstallStep( $callback, $findStep = 'BEGINNING' ) {
1801          $this->extraInstallSteps[$findStep][] = $callback;
1802      }
1803  
1804      /**
1805       * Disable the time limit for execution.
1806       * Some long-running pages (Install, Upgrade) will want to do this
1807       */
1808  	protected function disableTimeLimit() {
1809          wfSuppressWarnings();
1810          set_time_limit( 0 );
1811          wfRestoreWarnings();
1812      }
1813  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1