Skip to content
Snippets Groups Projects
index.php 68.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • Kevin Cristiano's avatar
    Kevin Cristiano committed
    <?php
    
    /**
     * Note that this installer has been based of the SilverStripe installer.
     * You can get more information from the SilverStripe Website at
     * http://www.silverstripe.com/.
     *
     * Copyright (c) 2006-7, SilverStripe Limited - www.silverstripe.com
     * All rights reserved.
     *
     * License: BSD-3-clause
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions are
     * met:
     *
     *  Redistributions of source code must retain the above copyright notice,
     *  this list of conditions and the following disclaimer.
     *
     *  Redistributions in binary form must reproduce the above copyright
     *  notice, this list of conditions and the following disclaimer in the
     *  documentation and/or other materials provided with the distribution.
     *
     *  Neither the name of SilverStripe nor the names of its contributors may
     *  be used to endorse or promote products derived from this software
     *  without specific prior written permission.
     *
     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
     * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
     * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
     * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     *
    
     * Changes and modifications (c) 2007-2017 by CiviCRM LLC
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
     *
     */
    
    /**
     * CiviCRM Installer
     */
    ini_set('max_execution_time', 3000);
    
    if (stristr(PHP_OS, 'WIN')) {
      define('CIVICRM_DIRECTORY_SEPARATOR', '/');
      define('CIVICRM_WINDOWS', 1);
    }
    else {
      define('CIVICRM_DIRECTORY_SEPARATOR', DIRECTORY_SEPARATOR);
      define('CIVICRM_WINDOWS', 0);
    }
    
    global $installType;
    global $crmPath;
    global $pkgPath;
    global $installDirPath;
    global $installURLPath;
    
    
    // Set the install type
    // this is sent as a query string when the page is first loaded
    // and subsequently posted to the page as a hidden field
    
    // only permit acceptable installation types to prevent issues;
    $acceptableInstallTypes = ['drupal', 'wordpress', 'backdrop'];
    if (isset($_POST['civicrm_install_type']) && in_array($_POST['civicrm_install_type'], $acceptableInstallTypes)) {
    
      $installType = $_POST['civicrm_install_type'];
    }
    
    elseif (isset($_GET['civicrm_install_type']) && in_array(strtolower($_GET['civicrm_install_type']), $acceptableInstallTypes)) {
    
      $installType = strtolower($_GET['civicrm_install_type']);
    }
    else {
    
      // default value if not set and not an acceptable install type.
    
      $installType = "drupal";
    }
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
    
    if ($installType == 'drupal' || $installType == 'backdrop') {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
      $crmPath = dirname(dirname($_SERVER['SCRIPT_FILENAME']));
      $installDirPath = $installURLPath = '';
    }
    elseif ($installType == 'wordpress') {
      $crmPath = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'civicrm' . DIRECTORY_SEPARATOR . 'civicrm' . DIRECTORY_SEPARATOR;
      $installDirPath = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'civicrm' . DIRECTORY_SEPARATOR . 'civicrm' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR;
      $installURLPath = WP_PLUGIN_URL . DIRECTORY_SEPARATOR . 'civicrm' . DIRECTORY_SEPARATOR . 'civicrm' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR;
    }
    else {
      $errorTitle = "Oops! Unsupported installation mode";
      $errorMsg = sprintf('%s: unknown installation mode. Please refer to the online documentation for more information.', $installType);
      errorDisplayPage($errorTitle, $errorMsg, FALSE);
    }
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    $composerJsonPath = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'composer.json';
    if (file_exists($composerJsonPath)) {
      $composerJson = json_decode(file_get_contents($composerJsonPath), 1);
      $minPhpVer = preg_replace(';[~^];', '', $composerJson['require']['php']);
      if (!version_compare(phpversion(), $minPhpVer, '>=')) {
        errorDisplayPage('PHP Version Requirement', sprintf("CiviCRM requires PHP %s+. The web server is running PHP %s.", $minPhpVer, phpversion()), FALSE);
      }
    }
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    $pkgPath = $crmPath . DIRECTORY_SEPARATOR . 'packages';
    
    require_once $crmPath . '/CRM/Core/ClassLoader.php';
    CRM_Core_ClassLoader::singleton()->register();
    
    $loadGenerated = 0;
    if (isset($_POST['loadGenerated'])) {
      $loadGenerated = 1;
    }
    
    require_once dirname(__FILE__) . CIVICRM_DIRECTORY_SEPARATOR . 'langs.php';
    foreach ($langs as $locale => $_) {
      if ($locale == 'en_US') {
        continue;
      }
      if (!file_exists(implode(CIVICRM_DIRECTORY_SEPARATOR, array($crmPath, 'sql', "civicrm_data.$locale.mysql")))) {
        unset($langs[$locale]);
      }
    }
    
    
    // Set the CMS
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    // This is mostly sympbolic, since nothing we do during the install
    // really requires CIVICRM_UF to be defined.
    $installTypeToUF = array(
      'wordpress' => 'WordPress',
      'drupal' => 'Drupal',
    
      'backdrop' => 'Backdrop',
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    );
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    $uf = ($installTypeToUF[$installType] ?? 'Drupal');
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    define('CIVICRM_UF', $uf);
    
    
    // Set the Locale (required by CRM_Core_Config)
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    global $tsLocale;
    
    $tsLocale = 'en_US';
    $seedLanguage = 'en_US';
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    // Backwards compatibility with default location of l10n files
    if (!defined('CIVICRM_L10N_BASEDIR') && file_exists($crmPath . DIRECTORY_SEPARATOR . 'l10n')) {
      define('CIVICRM_L10N_BASEDIR', $crmPath . DIRECTORY_SEPARATOR . 'l10n');
    }
    
    
    // CRM-16801 This validates that seedLanguage is valid by looking in $langs.
    // NB: the variable is initial a $_REQUEST for the initial page reload,
    // then becomes a $_POST when the installation form is submitted.
    if (isset($_REQUEST['seedLanguage']) and isset($langs[$_REQUEST['seedLanguage']])) {
      $seedLanguage = $_REQUEST['seedLanguage'];
      $tsLocale = $_REQUEST['seedLanguage'];
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    CRM_Core_Config::singleton(FALSE);
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    $GLOBALS['civicrm_default_error_scope'] = NULL;
    
    // The translation files are in the parent directory (l10n)
    $i18n = CRM_Core_I18n::singleton();
    
    
    // Support for Arabic, Hebrew, Farsi, etc.
    // Used in the template.html
    $short_lang_code = CRM_Core_I18n_PseudoConstant::shortForLong($tsLocale);
    $text_direction = (CRM_Core_I18n::isLanguageRTL($tsLocale) ? 'rtl' : 'ltr');
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    global $cmsPath;
    if ($installType == 'drupal') {
      //CRM-6840 -don't force to install in sites/all/modules/
      $object = new CRM_Utils_System_Drupal();
      $cmsPath = $object->cmsRootPath();
    
      $siteDir = getSiteDir($cmsPath, $_SERVER['SCRIPT_FILENAME']);
      $alreadyInstalled = file_exists($cmsPath . CIVICRM_DIRECTORY_SEPARATOR .
        'sites' . CIVICRM_DIRECTORY_SEPARATOR .
        $siteDir . CIVICRM_DIRECTORY_SEPARATOR .
        'civicrm.settings.php'
      );
    }
    
    elseif ($installType == 'backdrop') {
      $object = new CRM_Utils_System_Backdrop();
      $cmsPath = $object->cmsRootPath();
      $siteDir = getSiteDir($cmsPath, $_SERVER['SCRIPT_FILENAME']);
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
      $alreadyInstalled = file_exists($cmsPath . CIVICRM_DIRECTORY_SEPARATOR . 'civicrm.settings.php');
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    elseif ($installType == 'wordpress') {
      $cmsPath = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'civicrm';
      $upload_dir = wp_upload_dir();
      $files_dirname = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'civicrm';
    
      $wp_civi_settings = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'civicrm' . DIRECTORY_SEPARATOR . 'civicrm.settings.php';
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
      $wp_civi_settings_deprectated = CIVICRM_PLUGIN_DIR . 'civicrm.settings.php';
      if (file_exists($wp_civi_settings_deprectated)) {
        $alreadyInstalled = $wp_civi_settings_deprectated;
      }
      elseif (file_exists($wp_civi_settings)) {
        $alreadyInstalled = $wp_civi_settings;
      }
    }
    
    if ($installType == 'drupal') {
      // Lets check only /modules/.
      $pattern = '/' . preg_quote(CIVICRM_DIRECTORY_SEPARATOR . 'modules', CIVICRM_DIRECTORY_SEPARATOR) . '/';
    
      if (!preg_match($pattern, str_replace("\\", "/", $_SERVER['SCRIPT_FILENAME']))) {
        $directory = implode(CIVICRM_DIRECTORY_SEPARATOR, array('sites', 'all', 'modules'));
        $errorTitle = ts("Oops! Please correct your install location");
        $errorMsg = ts("Please untar (uncompress) your downloaded copy of CiviCRM in the <strong>%1</strong> directory below your Drupal root directory.", array(1 => $directory));
        errorDisplayPage($errorTitle, $errorMsg);
      }
    }
    
    
    if ($installType == 'backdrop') {
      // Lets check only /modules/.
      $pattern = '/' . preg_quote(CIVICRM_DIRECTORY_SEPARATOR . 'modules', CIVICRM_DIRECTORY_SEPARATOR) . '/';
    
      if (!preg_match($pattern, str_replace("\\", "/", $_SERVER['SCRIPT_FILENAME']))) {
        $directory = 'modules';
        $errorTitle = ts("Oops! Please correct your install location");
        $errorMsg = ts("Please untar (uncompress) your downloaded copy of CiviCRM in the <strong>%1</strong> directory below your Drupal root directory.", array(1 => $directory));
        errorDisplayPage($errorTitle, $errorMsg);
      }
    }
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    // Exit with error if CiviCRM has already been installed.
    if ($alreadyInstalled) {
      $errorTitle = ts("Oops! CiviCRM is already installed");
      $settings_directory = $cmsPath;
    
      if ($installType == 'drupal') {
        $settings_directory = implode(CIVICRM_DIRECTORY_SEPARATOR, array(
          ts('[your Drupal root directory]'),
          'sites',
          $siteDir,
        ));
      }
    
      if ($installType == 'backdrop') {
        $settings_directory = implode(CIVICRM_DIRECTORY_SEPARATOR, array(
          ts('[your Backdrop root directory]'),
          $siteDir,
        ));
      }
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
      $docLink = CRM_Utils_System::docURL2('Installation and Upgrades', FALSE, ts('Installation Guide'), NULL, NULL, "wiki");
    
      $errorMsg = ts("CiviCRM has already been installed. <ul><li>To <strong>start over</strong>, you must delete or rename the existing CiviCRM settings file - <strong>civicrm.settings.php</strong> - from <strong>%1</strong>.</li><li>To <strong>upgrade an existing installation</strong>, refer to the online documentation: %2.</li></ul>", array(1 => $settings_directory, 2 => $docLink));
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
      errorDisplayPage($errorTitle, $errorMsg, FALSE);
    }
    
    $versionFile = $crmPath . CIVICRM_DIRECTORY_SEPARATOR . 'civicrm-version.php';
    if (file_exists($versionFile)) {
      require_once $versionFile;
      $civicrm_version = civicrmVersion();
    }
    else {
      $civicrm_version = 'unknown';
    }
    
    if ($installType == 'drupal') {
      // Ensure that they have downloaded the correct version of CiviCRM
      if ($civicrm_version['cms'] != 'Drupal' && $civicrm_version['cms'] != 'Drupal6') {
        $errorTitle = ts("Oops! Incorrect CiviCRM version");
        $errorMsg = ts("This installer can only be used for the Drupal version of CiviCRM.");
        errorDisplayPage($errorTitle, $errorMsg);
      }
    
      define('DRUPAL_ROOT', $cmsPath);
      $drupalVersionFiles = array(
        // D6
        implode(CIVICRM_DIRECTORY_SEPARATOR, array($cmsPath, 'modules', 'system', 'system.module')),
        // D7
        implode(CIVICRM_DIRECTORY_SEPARATOR, array($cmsPath, 'includes', 'bootstrap.inc')),
      );
      foreach ($drupalVersionFiles as $drupalVersionFile) {
        if (file_exists($drupalVersionFile)) {
          require_once $drupalVersionFile;
        }
      }
    
    
      // Bootstrap Drupal to get settings and user
      $base_root = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http';
      $base_root .= '://' . $_SERVER['HTTP_HOST'];
      $base_url = $base_root;
      drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
    
      // Check that user is logged in and has administrative permissions
      // This is necessary because the script exposes the database settings in the form and these could be viewed by unauthorised users
      if ((!function_exists('user_access')) || (!user_access('administer site configuration'))) {
        $errorTitle = ts("You don't have permission to access this page");
        $errorMsg = ts("The installer can only be run by a user with the permission to administer site configuration.");
        errorDisplayPage($errorTitle, $errorMsg);
        exit();
      }
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
      if (!defined('VERSION') or version_compare(VERSION, '6.0') < 0) {
        $errorTitle = ts("Oops! Incorrect Drupal version");
        $errorMsg = ts("This version of CiviCRM can only be used with Drupal 6.x or 7.x. Please ensure that '%1' exists if you are running Drupal 7.0 and over.", array(1 => implode("' or '", $drupalVersionFiles)));
        errorDisplayPage($errorTitle, $errorMsg);
      }
    }
    
    elseif ($installType == 'backdrop') {
      // Ensure that they have downloaded the correct version of CiviCRM
      if ($civicrm_version['cms'] != 'Backdrop') {
        $errorTitle = ts("Oops! Incorrect CiviCRM version");
        $errorMsg = ts("This installer can only be used for the Backdrop version of CiviCRM.");
        errorDisplayPage($errorTitle, $errorMsg);
      }
    
      define('BACKDROP_ROOT', $cmsPath);
    
      $backdropVersionFiles = array(
        // Backdrop
        implode(CIVICRM_DIRECTORY_SEPARATOR, array($cmsPath, 'core', 'includes', 'bootstrap.inc')),
      );
      foreach ($backdropVersionFiles as $backdropVersionFile) {
        if (file_exists($backdropVersionFile)) {
          require_once $backdropVersionFile;
        }
      }
      if (!defined('BACKDROP_VERSION') or version_compare(BACKDROP_VERSION, '1.0') < 0) {
        $errorTitle = ts("Oops! Incorrect Backdrop version");
        $errorMsg = ts("This version of CiviCRM can only be used with Backdrop 1.x. Please ensure that '%1' exists if you are running Backdrop 1.0 and over.", array(1 => implode("' or '", $backdropVersionFiles)));
        errorDisplayPage($errorTitle, $errorMsg);
      }
    }
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    elseif ($installType == 'wordpress') {
      //HACK for now
      $civicrm_version['cms'] = 'WordPress';
    
      // Ensure that they have downloaded the correct version of CiviCRM
      if ($civicrm_version['cms'] != 'WordPress') {
        $errorTitle = ts("Oops! Incorrect CiviCRM version");
        $errorMsg = ts("This installer can only be used for the WordPress version of CiviCRM.");
        errorDisplayPage($errorTitle, $errorMsg);
      }
    }
    
    
    // Load CiviCRM database config
    if (isset($_POST['mysql'])) {
      $databaseConfig = $_POST['mysql'];
    }
    
    if ($installType == 'wordpress') {
      // Load WP database config
      if (isset($_POST['mysql'])) {
        $databaseConfig = $_POST['mysql'];
      }
      else {
        $databaseConfig = array(
          "server" => DB_HOST,
          "username" => DB_USER,
          "password" => DB_PASSWORD,
          "database" => DB_NAME,
        );
      }
    }
    
    if ($installType == 'drupal') {
      // Load drupal database config
      if (isset($_POST['drupal'])) {
        $drupalConfig = $_POST['drupal'];
      }
      else {
        $dbServer = $databases['default']['default']['host'];
        if (!empty($databases['default']['default']['port'])) {
          $dbServer .= ':' . $databases['default']['default']['port'];
        }
        $drupalConfig = array(
          "server" => $dbServer,
          "username" => $databases['default']['default']['username'],
          "password" => $databases['default']['default']['password'],
          "database" => $databases['default']['default']['database'],
        );
      }
    }
    
    if ($installType == 'backdrop') {
      // Load backdrop database config
      if (isset($_POST['backdrop'])) {
        $backdropConfig = $_POST['backdrop'];
      }
      else {
        $backdropConfig = array(
          "server" => "localhost",
          "username" => "backdrop",
          "password" => "",
          "database" => "backdrop",
        );
      }
    }
    
    // By default set CiviCRM database to be same as CMS database
    if (!isset($databaseConfig)) {
      if (($installType == 'drupal') && (isset($drupalConfig))) {
        $databaseConfig = $drupalConfig;
      }
      if (($installType == 'backdrop') && (isset($backdropConfig))) {
        $databaseConfig = $backdropConfig;
      }
    }
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    // Check requirements
    $req = new InstallRequirements();
    $req->check();
    
    if ($req->hasErrors()) {
      $hasErrorOtherThanDatabase = TRUE;
    }
    
    if ($databaseConfig) {
      $dbReq = new InstallRequirements();
      $dbReq->checkdatabase($databaseConfig, 'CiviCRM');
      if ($installType == 'drupal') {
        $dbReq->checkdatabase($drupalConfig, 'Drupal');
      }
    
      if ($installType == 'backdrop') {
        $dbReq->checkdatabase($backdropConfig, 'Backdrop');
      }
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    }
    
    // Actual processor
    if (isset($_POST['go']) && !$req->hasErrors() && !$dbReq->hasErrors()) {
      // Confirm before reinstalling
      if (!isset($_POST['force_reinstall']) && $alreadyInstalled) {
        include $installDirPath . 'template.html';
      }
      else {
        $inst = new Installer();
        $inst->install($_POST);
      }
    
      // Show the config form
    }
    else {
      include $installDirPath . 'template.html';
    }
    
    /**
     * This class checks requirements
     * Each of the requireXXX functions takes an argument which gives a user description of the test.  It's an array
     * of 3 parts:
     *  $description[0] - The test category
     *  $description[1] - The test title
     *  $description[2] - The test error to show, if it goes wrong
     */
    class InstallRequirements {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
      public $errors;
      public $warnings;
      public $tests;
      public $conn;
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
      // @see CRM_Upgrade_Form::MINIMUM_THREAD_STACK
      const MINIMUM_THREAD_STACK = 192;
    
      /**
       * Just check that the database configuration is okay.
       * @param $databaseConfig
       * @param $dbName
       */
      public function checkdatabase($databaseConfig, $dbName) {
    
        if ($this->requireFunction('mysqli_connect',
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          array(
            ts("PHP Configuration"),
            ts("MySQL support"),
            ts("MySQL support not included in PHP."),
          )
        )
        ) {
          $this->requireMySQLServer($databaseConfig['server'],
            array(
              ts("MySQL %1 Configuration", array(1 => $dbName)),
              ts("Does the server exist?"),
              ts("Can't find the a MySQL server on '%1'.", array(1 => $databaseConfig['server'])),
              $databaseConfig['server'],
            )
          );
          if ($this->requireMysqlConnection($databaseConfig['server'],
            $databaseConfig['username'],
            $databaseConfig['password'],
            array(
              ts("MySQL %1 Configuration", array(1 => $dbName)),
              ts("Are the access credentials correct?"),
              ts("That username/password doesn't work"),
            )
          )
          ) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
            @$this->requireMySQLVersion(CRM_Upgrade_Incremental_General::MIN_INSTALL_MYSQL_VER,
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
              array(
                ts("MySQL %1 Configuration", array(1 => $dbName)),
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
                ts("MySQL version at least %1", array(1 => CRM_Upgrade_Incremental_General::MIN_INSTALL_MYSQL_VER)),
                ts("MySQL version %1 or higher is required, you are running MySQL %2.", array(1 => CRM_Upgrade_Incremental_General::MIN_INSTALL_MYSQL_VER, 2 => mysqli_get_server_info($this->conn))),
    
                ts("MySQL %1", array(1 => mysqli_get_server_info($this->conn))),
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
              )
            );
            $this->requireMySQLAutoIncrementIncrementOne($databaseConfig['server'],
              $databaseConfig['username'],
              $databaseConfig['password'],
              array(
                ts("MySQL %1 Configuration", array(1 => $dbName)),
                ts("Is auto_increment_increment set to 1"),
                ts("An auto_increment_increment value greater than 1 is not currently supported. Please see issue CRM-7923 for further details and potential workaround."),
              )
            );
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
            $testDetails = array(
              ts("MySQL %1 Configuration", array(1 => $dbName)),
              ts("Is the provided database name valid?"),
    
              ts("The database name provided is not valid. Please use only 0-9, a-z, A-Z, _ and - as characters in the name."),
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
            );
    
            if (!CRM_Core_DAO::requireSafeDBName($databaseConfig['database'])) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
              $this->error($testDetails);
              return FALSE;
            }
            else {
              $this->testing($testDetails);
            }
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
            $this->requireMySQLThreadStack($databaseConfig['server'],
              $databaseConfig['username'],
              $databaseConfig['password'],
              $databaseConfig['database'],
              self::MINIMUM_THREAD_STACK,
              array(
                ts("MySQL %1 Configuration", array(1 => $dbName)),
                ts("Does MySQL thread_stack meet minimum (%1k)", array(1 => self::MINIMUM_THREAD_STACK)),
                "",
                // "The MySQL thread_stack does not meet minimum " . CRM_Upgrade_Form::MINIMUM_THREAD_STACK . "k. Please update thread_stack in my.cnf.",
              )
            );
          }
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          $onlyRequire = $dbName == 'Drupal' || $dbName == 'Backdrop';
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          $this->requireDatabaseOrCreatePermissions(
            $databaseConfig['server'],
            $databaseConfig['username'],
            $databaseConfig['password'],
            $databaseConfig['database'],
            array(
              ts("MySQL %1 Configuration", array(1 => $dbName)),
              ts("Can I access/create the database?"),
              ts("I can't create new databases and the database '%1' doesn't exist.", array(1 => $databaseConfig['database'])),
            ),
            $onlyRequire
          );
    
          if ($dbName != 'Drupal' && $dbName != 'Backdrop') {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
            $this->requireNoExistingData(
              $databaseConfig['server'],
              $databaseConfig['username'],
              $databaseConfig['password'],
              $databaseConfig['database'],
              array(
                ts("MySQL %1 Configuration", array(1 => $dbName)),
                ts("Does the database have data from a previous installation?"),
                ts("CiviCRM data from previous installation exists in '%1'.", array(1 => $databaseConfig['database'])),
              )
            );
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
            $this->requireMySQLInnoDB($databaseConfig['server'],
              $databaseConfig['username'],
              $databaseConfig['password'],
              $databaseConfig['database'],
              array(
                ts("MySQL %1 Configuration", array(1 => $dbName)),
                ts("Can I access/create InnoDB tables in the database?"),
                ts("Unable to create InnoDB tables. MySQL InnoDB support is required for CiviCRM but is either not available or not enabled in this MySQL database server."),
              )
            );
            $this->requireMySQLTempTables($databaseConfig['server'],
              $databaseConfig['username'],
              $databaseConfig['password'],
              $databaseConfig['database'],
              array(
                ts("MySQL %1 Configuration", array(1 => $dbName)),
                ts('Can I create temporary tables in the database?'),
                ts('Unable to create temporary tables. This MySQL user is missing the CREATE TEMPORARY TABLES privilege.'),
              )
            );
            $this->requireMySQLLockTables($databaseConfig['server'],
              $databaseConfig['username'],
              $databaseConfig['password'],
              $databaseConfig['database'],
              array(
                ts("MySQL %1 Configuration", array(1 => $dbName)),
                ts('Can I create lock tables in the database?'),
                ts('Unable to lock tables. This MySQL user is missing the LOCK TABLES privilege.'),
              )
            );
            $this->requireMySQLTrigger($databaseConfig['server'],
              $databaseConfig['username'],
              $databaseConfig['password'],
              $databaseConfig['database'],
              array(
                ts("MySQL %1 Configuration", array(1 => $dbName)),
                ts('Can I create triggers in the database?'),
                ts('Unable to create triggers. This MySQL user is missing the CREATE TRIGGERS  privilege.'),
              )
            );
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
            $this->requireMySQLUtf8mb4($databaseConfig['server'],
              $databaseConfig['username'],
              $databaseConfig['password'],
              $databaseConfig['database'],
              array(
                ts("MySQL %1 Configuration", array(1 => $dbName)),
                ts('Is the <code>utf8mb4</code> character set supported?'),
                ts('This MySQL server does not support the <code>utf8mb4</code> character set.'),
              )
            );
    
      /**
       * Connect via mysqli.
       *
       * This is exactly the same as mysqli_connect(), except that it accepts
       * the port as part of the `$host`.
       *
       * @param string $host
       *   Ex: 'localhost', 'localhost:3307', '127.0.0.1:3307', '[::1]', '[::1]:3307'.
       * @param string $username
       * @param string $password
       * @param string $database
       * @return \mysqli
       */
      protected function connect($host, $username, $password, $database = '') {
        $hostParts = explode(':', $host);
        if (count($hostParts) > 1 && strrpos($host, ']') !== strlen($host) - 1) {
          $port = array_pop($hostParts);
          $host = implode(':', $hostParts);
        }
        else {
    
          $port = NULL;
    
        }
        $conn = @mysqli_connect($host, $username, $password, $database, $port);
        return $conn;
      }
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
      /**
       * Check everything except the database.
       */
      public function check() {
        global $crmPath, $installType;
    
        $this->errors = NULL;
    
    
        $this->requirePHPVersion(array(
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          ts("PHP Configuration"),
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          ts("PHP7 installed"),
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        ));
    
        // Check that we can identify the root folder successfully
    
        $this->requireFile($crmPath . CIVICRM_DIRECTORY_SEPARATOR . 'README.md',
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          array(
            ts("File permissions"),
            ts("Does the webserver know where files are stored?"),
            ts("The webserver isn't letting me identify where files are stored."),
            $this->getBaseDir(),
          ),
          TRUE
        );
    
        // CRM-6485: make sure the path does not contain PATH_SEPARATOR, as we don’t know how to escape it
        $this->requireNoPathSeparator(
          array(
            ts("File permissions"),
            ts('Does the CiviCRM path contain PATH_SEPARATOR?'),
            ts('The path %1 contains PATH_SEPARATOR (the %2 character).', array(1 => $this->getBaseDir(), 2 => PATH_SEPARATOR)),
            $this->getBaseDir(),
          )
        );
    
        $requiredDirectories = array('CRM', 'packages', 'templates', 'js', 'api', 'i', 'sql');
        foreach ($requiredDirectories as $dir) {
          $this->requireFile($crmPath . CIVICRM_DIRECTORY_SEPARATOR . $dir,
            array(
              ts("File permissions"),
              ts("Folder '%1' exists?", array(1 => $dir)),
              ts("There is no '%1' folder.", array(1 => $dir)),
            ), TRUE
          );
        }
    
        $configIDSiniDir = NULL;
        global $cmsPath;
        $siteDir = getSiteDir($cmsPath, $_SERVER['SCRIPT_FILENAME']);
        if ($installType == 'drupal') {
    
          // make sure that we can write to sites/default and files/
          $writableDirectories = array(
            $cmsPath . CIVICRM_DIRECTORY_SEPARATOR .
            'sites' . CIVICRM_DIRECTORY_SEPARATOR .
            $siteDir . CIVICRM_DIRECTORY_SEPARATOR .
            'files',
            $cmsPath . CIVICRM_DIRECTORY_SEPARATOR .
            'sites' . CIVICRM_DIRECTORY_SEPARATOR .
            $siteDir,
          );
        }
    
        elseif ($installType == 'backdrop') {
    
          // make sure that we can write to sites/default and files/
          $writableDirectories = array(
            $cmsPath . CIVICRM_DIRECTORY_SEPARATOR .
            'files',
            $cmsPath,
          );
        }
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        elseif ($installType == 'wordpress') {
          // make sure that we can write to uploads/civicrm/
          $upload_dir = wp_upload_dir();
          $files_dirname = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'civicrm';
          if (!file_exists($files_dirname)) {
            wp_mkdir_p($files_dirname);
          }
          $writableDirectories = array($files_dirname);
        }
    
        foreach ($writableDirectories as $dir) {
          $dirName = CIVICRM_WINDOWS ? $dir : CIVICRM_DIRECTORY_SEPARATOR . $dir;
          $testDetails = array(
            ts("File permissions"),
            ts("Is the %1 folder writeable?", array(1 => $dir)),
            NULL,
          );
          $this->requireWriteable($dirName, $testDetails, TRUE);
        }
    
        // Check for rewriting
        if (isset($_SERVER['SERVER_SOFTWARE'])) {
          $webserver = strip_tags(trim($_SERVER['SERVER_SOFTWARE']));
        }
        elseif (isset($_SERVER['SERVER_SIGNATURE'])) {
          $webserver = strip_tags(trim($_SERVER['SERVER_SIGNATURE']));
        }
    
        if ($webserver == '') {
          $webserver = ts("I can't tell what webserver you are running");
        }
    
        // Check for $_SERVER configuration
        $this->requireServerVariables(array('SCRIPT_NAME', 'HTTP_HOST', 'SCRIPT_FILENAME'), array(
          ts("Webserver config"),
          ts("Recognised webserver"),
          ts("You seem to be using an unsupported webserver. The server variables SCRIPT_NAME, HTTP_HOST, SCRIPT_FILENAME need to be set."),
        ));
    
        // Check for MySQL support
    
        $this->requireFunction('mysqli_connect', array(
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          ts("PHP Configuration"),
          ts("MySQL support"),
          ts("MySQL support not included in PHP."),
        ));
    
    
        // Check for XML support
        $this->requireFunction('simplexml_load_file', array(
          ts("PHP Configuration"),
          ts("SimpleXML support"),
          ts("SimpleXML support not included in PHP."),
        ));
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Check for JSON support
        $this->requireFunction('json_encode', array(
          ts("PHP Configuration"),
          ts("JSON support"),
          ts("JSON support not included in PHP."),
        ));
    
    
        // check for Multibyte support such as mb_substr. Required for proper handling of Multilingual setups.
        $this->requireFunction('mb_substr', array(
          ts("PHP Configuration"),
          ts("Multibyte support"),
          ts("Multibyte support not enabled in PHP."),
        ));
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Check for xcache_isset and emit warning if exists
        $this->checkXCache(array(
          ts("PHP Configuration"),
          ts("XCache compatibility"),
          ts("XCache is installed and there are known compatibility issues between XCache and CiviCRM. Consider using an alternative PHP caching mechanism or disable PHP caching altogether."),
        ));
    
        // Check memory allocation
        $this->requireMemory(32 * 1024 * 1024,
          64 * 1024 * 1024,
          array(
            ts("PHP Configuration"),
            ts("Memory allocated (PHP config option 'memory_limit')"),
            ts("CiviCRM needs a minimum of %1 MB allocated to PHP, but recommends %2 MB.", array(1 => 32, 2 => 64)),
            ini_get("memory_limit"),
          )
        );
    
        return $this->errors;
      }
    
      /**
       * @param $min
       * @param $recommended
       * @param $testDetails
       */
      public function requireMemory($min, $recommended, $testDetails) {
        $this->testing($testDetails);
        $mem = $this->getPHPMemory();
    
        if ($mem < $min && $mem > 0) {
          $testDetails[2] .= " " . ts("You only have %1 allocated", array(1 => ini_get("memory_limit")));
          $this->error($testDetails);
        }
        elseif ($mem < $recommended && $mem > 0) {
          $testDetails[2] .= " " . ts("You only have %1 allocated", array(1 => ini_get("memory_limit")));
          $this->warning($testDetails);
        }
        elseif ($mem == 0) {
          $testDetails[2] .= " " . ts("We can't determine how much memory you have allocated. Install only if you're sure you've allocated at least %1 MB.", array(1 => 32));
          $this->warning($testDetails);
        }
      }
    
      /**
       * @return float
       */
      public function getPHPMemory() {
        $memString = ini_get("memory_limit");
    
        switch (strtolower(substr($memString, -1))) {
          case "k":
            return round(substr($memString, 0, -1) * 1024);
    
          case "m":
            return round(substr($memString, 0, -1) * 1024 * 1024);
    
          case "g":
            return round(substr($memString, 0, -1) * 1024 * 1024 * 1024);
    
          default:
            return round($memString);
        }
      }
    
      public function listErrors() {
        if ($this->errors) {
          echo "<p>" . ts("The following problems are preventing me from installing CiviCRM:") . "</p>";
          foreach ($this->errors as $error) {
            echo "<li>" . htmlentities($error) . "</li>";
          }
        }
      }
    
      /**
       * @param null $section
       */
      public function showTable($section = NULL) {
        if ($section) {
          $tests = $this->tests[$section];
          echo "<table class=\"testResults\" width=\"100%\">";
          foreach ($tests as $test => $result) {
            echo "<tr class=\"$result[0]\"><td>$test</td><td>" . nl2br(htmlentities($result[1])) . "</td></tr>";
          }
          echo "</table>";
        }
        else {
          foreach ($this->tests as $section => $tests) {
            echo "<h3>$section</h3>";
            echo "<table class=\"testResults\" width=\"100%\">";
    
            foreach ($tests as $test => $result) {
              echo "<tr class=\"$result[0]\"><td>$test</td><td>" . nl2br(htmlentities($result[1])) . "</td></tr>";
            }
            echo "</table>";
          }
        }
      }
    
      /**
       * @param string $funcName
       * @param $testDetails
       *
       * @return bool
       */
      public function requireFunction($funcName, $testDetails) {
        $this->testing($testDetails);
    
        if (!function_exists($funcName)) {
          $this->error($testDetails);
          return FALSE;
        }
        else {
          return TRUE;
        }
      }
    
      /**
       * @param $testDetails
       */
      public function checkXCache($testDetails) {
        if (function_exists('xcache_isset') &&
          ini_get('xcache.size') > 0
        ) {
          $this->testing($testDetails);
          $this->warning($testDetails);
        }
      }
    
      /**
    
       * @param array $testDetails
       * @return bool
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       */
    
      public function requirePHPVersion($testDetails) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
        $this->testing($testDetails);
    
        $phpVersion = phpversion();
    
        $aboveMinVersion = version_compare($phpVersion, CRM_Upgrade_Incremental_General::MIN_INSTALL_PHP_VER) >= 0;
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
    
        if ($aboveMinVersion) {
          if (version_compare($phpVersion, CRM_Upgrade_Incremental_General::MIN_RECOMMENDED_PHP_VER) < 0) {
            $testDetails[2] = ts('This webserver is running an outdated version of PHP (%1). It is strongly recommended to upgrade to PHP %2 or later, as older versions can present a security risk. The preferred version is %3.', array(
              1 => $phpVersion,
    
              2 => CRM_Upgrade_Incremental_General::MIN_RECOMMENDED_PHP_VER,
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
              3 => preg_replace(';^(\d+\.\d+(?:\.[1-9]\d*)?).*$;', '\1', CRM_Upgrade_Incremental_General::RECOMMENDED_PHP_VER),
    
            ));
            $this->warning($testDetails);
          }
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          return TRUE;
        }
    
    
        if (empty($testDetails[2])) {
          $testDetails[2] = ts("You need PHP version %1 or later, only %2 is installed. Please upgrade your server, or ask your web-host to do so.", array(1 => CRM_Upgrade_Incremental_General::MIN_INSTALL_PHP_VER, 2 => $phpVersion));
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        }
    
        $this->error($testDetails);
      }
    
      /**
       * @param string $filename
       * @param $testDetails
       * @param bool $absolute
       */
      public function requireFile($filename, $testDetails, $absolute = FALSE) {
        $this->testing($testDetails);
        if (!$absolute) {
          $filename = $this->getBaseDir() . $filename;
        }
        if (!file_exists($filename)) {
          $testDetails[2] .= " (" . ts("file '%1' not found", array(1 => $filename)) . ')';
          $this->error($testDetails);
        }
      }
    
      /**
       * @param $testDetails
       */
      public function requireNoPathSeparator($testDetails) {
        $this->testing($testDetails);
        if (substr_count($this->getBaseDir(), PATH_SEPARATOR)) {
          $this->error($testDetails);
        }
      }
    
      /**
       * @param string $filename
       * @param $testDetails
       */
      public function requireNoFile($filename, $testDetails) {
        $this->testing($testDetails);
        $filename = $this->getBaseDir() . $filename;
        if (file_exists($filename)) {
          $testDetails[2] .= " (" . ts("file '%1' found", array(1 => $filename)) . ")";
          $this->error($testDetails);
        }
      }
    
      /**
       * @param string $filename
       * @param $testDetails
       */
      public function moveFileOutOfTheWay($filename, $testDetails) {
        $this->testing($testDetails);
        $filename = $this->getBaseDir() . $filename;
        if (file_exists($filename)) {
          if (file_exists("$filename.bak")) {
            rm("$filename.bak");
          }
          rename($filename, "$filename.bak");
        }
      }
    
      /**
       * @param string $filename
       * @param $testDetails
       * @param bool $absolute
       */
      public function requireWriteable($filename, $testDetails, $absolute = FALSE) {
        $this->testing($testDetails);
        if (!$absolute) {
          $filename = $this->getBaseDir() . $filename;
        }
    
        if (!is_writable($filename)) {
          $name = NULL;
          if (function_exists('posix_getpwuid')) {
            $user = posix_getpwuid(posix_geteuid());
            $name = '- ' . $user['name'] . ' -';
          }
    
          if (!isset($testDetails[2])) {
            $testDetails[2] = NULL;
          }