index.php 68.3 KB
Newer Older
totten's avatar
totten committed
1 2 3 4 5
<?php

/**
 * Note that this installer has been based of the SilverStripe installer.
 * You can get more information from the SilverStripe Website at
6
 * http://www.silverstripe.com/.
totten's avatar
totten committed
7 8 9 10
 *
 * Copyright (c) 2006-7, SilverStripe Limited - www.silverstripe.com
 * All rights reserved.
 *
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
 * 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.
 *
yashodha's avatar
yashodha committed
39
 * Changes and modifications (c) 2007-2017 by CiviCRM LLC
totten's avatar
totten committed
40 41 42 43 44 45 46 47 48 49
 *
 */

/**
 * CiviCRM Installer
 */
ini_set('max_execution_time', 3000);

if (stristr(PHP_OS, 'WIN')) {
  define('CIVICRM_DIRECTORY_SEPARATOR', '/');
50
  define('CIVICRM_WINDOWS', 1);
totten's avatar
totten committed
51 52 53
}
else {
  define('CIVICRM_DIRECTORY_SEPARATOR', DIRECTORY_SEPARATOR);
54
  define('CIVICRM_WINDOWS', 0);
totten's avatar
totten committed
55 56 57 58
}

global $installType;
global $crmPath;
59
global $pkgPath;
totten's avatar
totten committed
60 61
global $installDirPath;
global $installURLPath;
62

63 64 65
// 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
66 67 68
// 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)) {
69 70
  $installType = $_POST['civicrm_install_type'];
}
71
elseif (isset($_GET['civicrm_install_type']) && in_array(strtolower($_GET['civicrm_install_type']), $acceptableInstallTypes)) {
72 73 74
  $installType = strtolower($_GET['civicrm_install_type']);
}
else {
75
  // default value if not set and not an acceptable install type.
76 77
  $installType = "drupal";
}
78

79
if ($installType == 'drupal' || $installType == 'backdrop') {
totten's avatar
totten committed
80 81 82 83 84 85 86 87
  $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;
}
88 89 90 91 92
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);
}
totten's avatar
totten committed
93

94 95
$pkgPath = $crmPath . DIRECTORY_SEPARATOR . 'packages';

totten's avatar
totten committed
96 97 98 99
require_once $crmPath . '/CRM/Core/ClassLoader.php';
CRM_Core_ClassLoader::singleton()->register();

$loadGenerated = 0;
100
if (isset($_POST['loadGenerated'])) {
totten's avatar
totten committed
101 102 103 104 105 106 107 108
  $loadGenerated = 1;
}

require_once dirname(__FILE__) . CIVICRM_DIRECTORY_SEPARATOR . 'langs.php';
foreach ($langs as $locale => $_) {
  if ($locale == 'en_US') {
    continue;
  }
totten's avatar
totten committed
109 110
  if (!file_exists(implode(CIVICRM_DIRECTORY_SEPARATOR, array($crmPath, 'sql', "civicrm_data.$locale.mysql")))) {
    unset($langs[$locale]);
totten's avatar
totten committed
111
  }
totten's avatar
totten committed
112 113
}

114
// Set the CMS
115 116 117 118 119
// This is mostly sympbolic, since nothing we do during the install
// really requires CIVICRM_UF to be defined.
$installTypeToUF = array(
  'wordpress' => 'WordPress',
  'drupal' => 'Drupal',
120
  'backdrop' => 'Backdrop',
121 122
);

123
$uf = (isset($installTypeToUF[$installType]) ? $installTypeToUF[$installType] : 'Drupal');
124 125
define('CIVICRM_UF', $uf);

126
// Set the Locale (required by CRM_Core_Config)
127 128 129
global $tsLocale;

$tsLocale = 'en_US';
totten's avatar
totten committed
130
$seedLanguage = 'en_US';
131

132 133 134 135 136 137
// 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'];
totten's avatar
totten committed
138 139
}

140
$config = CRM_Core_Config::singleton(FALSE);
141 142
$GLOBALS['civicrm_default_error_scope'] = NULL;

143 144 145
// The translation files are in the parent directory (l10n)
$i18n = CRM_Core_I18n::singleton();

146 147 148 149 150
// 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');

totten's avatar
totten committed
151 152 153 154 155 156 157 158 159 160 161 162 163
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'
  );
}
164 165 166 167
elseif ($installType == 'backdrop') {
  $object = new CRM_Utils_System_Backdrop();
  $cmsPath = $object->cmsRootPath();
  $siteDir = getSiteDir($cmsPath, $_SERVER['SCRIPT_FILENAME']);
168
  $alreadyInstalled = file_exists($cmsPath . CIVICRM_DIRECTORY_SEPARATOR . 'civicrm.settings.php');
169
}
totten's avatar
totten committed
170 171
elseif ($installType == 'wordpress') {
  $cmsPath = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'civicrm';
172 173
  $upload_dir = wp_upload_dir();
  $files_dirname = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'civicrm';
174
  $wp_civi_settings = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'civicrm' . DIRECTORY_SEPARATOR . 'civicrm.settings.php';
175 176 177 178 179 180 181
  $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;
  }
totten's avatar
totten committed
182 183
}

184 185 186 187 188 189 190 191 192 193 194 195
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);
  }
}

196 197 198 199 200 201 202 203 204 205 206 207
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);
  }
}

totten's avatar
totten committed
208 209
// Exit with error if CiviCRM has already been installed.
if ($alreadyInstalled) {
210 211
  $errorTitle = ts("Oops! CiviCRM is already installed");
  $settings_directory = $cmsPath;
totten's avatar
totten committed
212

213 214 215 216 217 218
  if ($installType == 'drupal') {
    $settings_directory = implode(CIVICRM_DIRECTORY_SEPARATOR, array(
      ts('[your Drupal root directory]'),
      'sites',
      $siteDir,
    ));
totten's avatar
totten committed
219
  }
220 221 222 223 224 225
  if ($installType == 'backdrop') {
    $settings_directory = implode(CIVICRM_DIRECTORY_SEPARATOR, array(
      ts('[your Backdrop root directory]'),
      $siteDir,
    ));
  }
226 227

  $docLink = CRM_Utils_System::docURL2('Installation and Upgrades', FALSE, ts('Installation Guide'), NULL, NULL, "wiki");
wmortada's avatar
wmortada committed
228
  $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));
229
  errorDisplayPage($errorTitle, $errorMsg, FALSE);
totten's avatar
totten committed
230 231 232 233
}

$versionFile = $crmPath . CIVICRM_DIRECTORY_SEPARATOR . 'civicrm-version.php';
if (file_exists($versionFile)) {
totten's avatar
totten committed
234
  require_once $versionFile;
totten's avatar
totten committed
235 236 237 238 239 240 241 242
  $civicrm_version = civicrmVersion();
}
else {
  $civicrm_version = 'unknown';
}

if ($installType == 'drupal') {
  // Ensure that they have downloaded the correct version of CiviCRM
243 244 245
  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.");
totten's avatar
totten committed
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
    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;
    }
  }

262 263 264 265 266 267 268 269 270 271 272 273 274 275
  // 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();
  }
276

totten's avatar
totten committed
277
  if (!defined('VERSION') or version_compare(VERSION, '6.0') < 0) {
278 279
    $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)));
totten's avatar
totten committed
280 281 282
    errorDisplayPage($errorTitle, $errorMsg);
  }
}
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
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);
  }
}
totten's avatar
totten committed
308 309 310 311 312 313
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') {
314 315
    $errorTitle = ts("Oops! Incorrect CiviCRM version");
    $errorMsg = ts("This installer can only be used for the WordPress version of CiviCRM.");
totten's avatar
totten committed
316 317 318 319
    errorDisplayPage($errorTitle, $errorMsg);
  }
}

320
// Load CiviCRM database config
321
if (isset($_POST['mysql'])) {
wmortada's avatar
wmortada committed
322
  $databaseConfig = $_POST['mysql'];
323 324 325
}

if ($installType == 'wordpress') {
wmortada's avatar
wmortada committed
326 327 328 329 330
  // Load WP database config
  if (isset($_POST['mysql'])) {
    $databaseConfig = $_POST['mysql'];
  }
  else {
331
    $databaseConfig = array(
wmortada's avatar
wmortada committed
332 333 334 335
      "server" => DB_HOST,
      "username" => DB_USER,
      "password" => DB_PASSWORD,
      "database" => DB_NAME,
336
    );
wmortada's avatar
wmortada committed
337
  }
338 339 340
}

if ($installType == 'drupal') {
wmortada's avatar
wmortada committed
341 342 343 344 345
  // Load drupal database config
  if (isset($_POST['drupal'])) {
    $drupalConfig = $_POST['drupal'];
  }
  else {
346 347 348 349
    $dbServer = $databases['default']['default']['host'];
    if (!empty($databases['default']['default']['port'])) {
      $dbServer .= ':' . $databases['default']['default']['port'];
    }
350
    $drupalConfig = array(
351
      "server" => $dbServer,
wmortada's avatar
wmortada committed
352 353 354
      "username" => $databases['default']['default']['username'],
      "password" => $databases['default']['default']['password'],
      "database" => $databases['default']['default']['database'],
355
    );
wmortada's avatar
wmortada committed
356
  }
357 358 359
}

if ($installType == 'backdrop') {
wmortada's avatar
wmortada committed
360 361 362 363 364
  // Load backdrop database config
  if (isset($_POST['backdrop'])) {
    $backdropConfig = $_POST['backdrop'];
  }
  else {
365
    $backdropConfig = array(
wmortada's avatar
wmortada committed
366 367 368 369
      "server" => "localhost",
      "username" => "backdrop",
      "password" => "",
      "database" => "backdrop",
370
    );
wmortada's avatar
wmortada committed
371
  }
372 373
}

374 375 376 377 378 379 380 381 382 383
// 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;
  }
}

totten's avatar
totten committed
384 385 386 387 388 389 390 391 392 393 394 395 396 397
// 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');
  }
398 399 400
  if ($installType == 'backdrop') {
    $dbReq->checkdatabase($backdropConfig, 'Backdrop');
  }
totten's avatar
totten committed
401 402 403
}

// Actual processor
404
if (isset($_POST['go']) && !$req->hasErrors() && !$dbReq->hasErrors()) {
totten's avatar
totten committed
405
  // Confirm before reinstalling
406
  if (!isset($_POST['force_reinstall']) && $alreadyInstalled) {
totten's avatar
totten committed
407
    include $installDirPath . 'template.html';
totten's avatar
totten committed
408 409 410
  }
  else {
    $inst = new Installer();
411
    $inst->install($_POST);
totten's avatar
totten committed
412 413 414 415 416
  }

  // Show the config form
}
else {
totten's avatar
totten committed
417
  include $installDirPath . 'template.html';
totten's avatar
totten committed
418 419 420 421 422 423
}

/**
 * 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:
424
 *  $description[0] - The test category
totten's avatar
totten committed
425 426 427 428
 *  $description[1] - The test title
 *  $description[2] - The test error to show, if it goes wrong
 */
class InstallRequirements {
429 430 431 432
  public $errors;
  public $warnings;
  public $tests;
  public $conn;
totten's avatar
totten committed
433 434 435 436 437

  // @see CRM_Upgrade_Form::MINIMUM_THREAD_STACK
  const MINIMUM_THREAD_STACK = 192;

  /**
438
   * Just check that the database configuration is okay.
439 440
   * @param $databaseConfig
   * @param $dbName
totten's avatar
totten committed
441
   */
colemanw's avatar
colemanw committed
442
  public function checkdatabase($databaseConfig, $dbName) {
443
    if ($this->requireFunction('mysqli_connect',
444
      array(
445 446 447
        ts("PHP Configuration"),
        ts("MySQL support"),
        ts("MySQL support not included in PHP."),
448 449 450
      )
    )
    ) {
totten's avatar
totten committed
451 452
      $this->requireMySQLServer($databaseConfig['server'],
        array(
453 454 455
          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'])),
totten's avatar
totten committed
456 457 458 459
          $databaseConfig['server'],
        )
      );
      if ($this->requireMysqlConnection($databaseConfig['server'],
460 461 462
        $databaseConfig['username'],
        $databaseConfig['password'],
        array(
463 464 465
          ts("MySQL %1 Configuration", array(1 => $dbName)),
          ts("Are the access credentials correct?"),
          ts("That username/password doesn't work"),
466 467 468
        )
      )
      ) {
totten's avatar
totten committed
469 470
        @$this->requireMySQLVersion("5.1",
          array(
471 472
            ts("MySQL %1 Configuration", array(1 => $dbName)),
            ts("MySQL version at least %1", array(1 => '5.1')),
473 474
            ts("MySQL version %1 or higher is required, you are running MySQL %2.", array(1 => '5.1', 2 => mysqli_get_server_info($this->conn))),
            ts("MySQL %1", array(1 => mysqli_get_server_info($this->conn))),
totten's avatar
totten committed
475 476 477 478 479 480
          )
        );
        $this->requireMySQLAutoIncrementIncrementOne($databaseConfig['server'],
          $databaseConfig['username'],
          $databaseConfig['password'],
          array(
481 482 483
            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."),
totten's avatar
totten committed
484 485
          )
        );
486 487 488
        $testDetails = array(
          ts("MySQL %1 Configuration", array(1 => $dbName)),
          ts("Is the provided database name valid?"),
489
          ts("The database name provided is not valid. Please use only 0-9, a-z, A-Z, _ and - as characters in the name."),
490
        );
491
        if (!CRM_Core_DAO::requireSafeDBName($databaseConfig['database'])) {
492
          $this->error($testDetails);
493 494
          return FALSE;
        }
495 496 497
        else {
          $this->testing($testDetails);
        }
totten's avatar
totten committed
498 499 500 501 502 503
        $this->requireMySQLThreadStack($databaseConfig['server'],
          $databaseConfig['username'],
          $databaseConfig['password'],
          $databaseConfig['database'],
          self::MINIMUM_THREAD_STACK,
          array(
504 505
            ts("MySQL %1 Configuration", array(1 => $dbName)),
            ts("Does MySQL thread_stack meet minimum (%1k)", array(1 => self::MINIMUM_THREAD_STACK)),
506 507
            "",
            // "The MySQL thread_stack does not meet minimum " . CRM_Upgrade_Form::MINIMUM_THREAD_STACK . "k. Please update thread_stack in my.cnf.",
totten's avatar
totten committed
508 509 510
          )
        );
      }
511
      $onlyRequire = ($dbName == 'Drupal' || $dbName == 'Backdrop') ? TRUE : FALSE;
totten's avatar
totten committed
512 513 514 515 516 517
      $this->requireDatabaseOrCreatePermissions(
        $databaseConfig['server'],
        $databaseConfig['username'],
        $databaseConfig['password'],
        $databaseConfig['database'],
        array(
518 519
          ts("MySQL %1 Configuration", array(1 => $dbName)),
          ts("Can I access/create the database?"),
totten's avatar
totten committed
520
          ts("I can't create new databases and the database '%1' doesn't exist.", array(1 => $databaseConfig['database'])),
totten's avatar
totten committed
521 522 523
        ),
        $onlyRequire
      );
524
      if ($dbName != 'Drupal' && $dbName != 'Backdrop') {
525 526 527 528 529 530 531
        $this->requireNoExistingData(
          $databaseConfig['server'],
          $databaseConfig['username'],
          $databaseConfig['password'],
          $databaseConfig['database'],
          array(
            ts("MySQL %1 Configuration", array(1 => $dbName)),
eileen's avatar
eileen committed
532
            ts("Does the database have data from a previous installation?"),
533 534 535
            ts("CiviCRM data from previous installation exists in '%1'.", array(1 => $databaseConfig['database'])),
          )
        );
totten's avatar
totten committed
536 537 538 539 540
        $this->requireMySQLInnoDB($databaseConfig['server'],
          $databaseConfig['username'],
          $databaseConfig['password'],
          $databaseConfig['database'],
          array(
541 542 543
            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."),
totten's avatar
totten committed
544 545 546 547 548 549 550
          )
        );
        $this->requireMySQLTempTables($databaseConfig['server'],
          $databaseConfig['username'],
          $databaseConfig['password'],
          $databaseConfig['database'],
          array(
551 552 553
            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.'),
totten's avatar
totten committed
554 555 556 557 558 559 560
          )
        );
        $this->requireMySQLLockTables($databaseConfig['server'],
          $databaseConfig['username'],
          $databaseConfig['password'],
          $databaseConfig['database'],
          array(
561 562 563
            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.'),
totten's avatar
totten committed
564 565 566 567 568 569 570
          )
        );
        $this->requireMySQLTrigger($databaseConfig['server'],
          $databaseConfig['username'],
          $databaseConfig['password'],
          $databaseConfig['database'],
          array(
571 572 573
            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.'),
totten's avatar
totten committed
574 575
          )
        );
576 577 578 579 580 581 582 583 584 585
        $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.'),
          )
        );
totten's avatar
totten committed
586 587 588 589
      }
    }
  }

590 591 592 593 594 595
  /**
   * Connect via mysqli.
   *
   * This is exactly the same as mysqli_connect(), except that it accepts
   * the port as part of the `$host`.
   *
596 597 598 599
   * @param string $host
   *   Ex: 'localhost', 'localhost:3307', '127.0.0.1:3307', '[::1]', '[::1]:3307'.
   * @param string $username
   * @param string $password
600 601 602 603 604
   * @param string $database
   * @return \mysqli
   */
  protected function connect($host, $username, $password, $database = '') {
    $hostParts = explode(':', $host);
605 606 607
    if (count($hostParts) > 1 && strrpos($host, ']') !== strlen($host) - 1) {
      $port = array_pop($hostParts);
      $host = implode(':', $hostParts);
608 609
    }
    else {
610
      $port = NULL;
611 612 613 614 615
    }
    $conn = @mysqli_connect($host, $username, $password, $database, $port);
    return $conn;
  }

totten's avatar
totten committed
616
  /**
617
   * Check everything except the database.
totten's avatar
totten committed
618
   */
colemanw's avatar
colemanw committed
619
  public function check() {
totten's avatar
totten committed
620 621 622 623
    global $crmPath, $installType;

    $this->errors = NULL;

624
    $this->requirePHPVersion(array(
625 626
      ts("PHP Configuration"),
      ts("PHP5 installed"),
627
    ));
totten's avatar
totten committed
628 629

    // Check that we can identify the root folder successfully
630
    $this->requireFile($crmPath . CIVICRM_DIRECTORY_SEPARATOR . 'README.md',
totten's avatar
totten committed
631
      array(
632 633 634
        ts("File permissions"),
        ts("Does the webserver know where files are stored?"),
        ts("The webserver isn't letting me identify where files are stored."),
totten's avatar
totten committed
635 636 637 638 639 640 641 642
        $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(
643 644 645
        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)),
totten's avatar
totten committed
646 647 648 649 650 651 652 653
        $this->getBaseDir(),
      )
    );

    $requiredDirectories = array('CRM', 'packages', 'templates', 'js', 'api', 'i', 'sql');
    foreach ($requiredDirectories as $dir) {
      $this->requireFile($crmPath . CIVICRM_DIRECTORY_SEPARATOR . $dir,
        array(
654 655 656
          ts("File permissions"),
          ts("Folder '%1' exists?", array(1 => $dir)),
          ts("There is no '%1' folder.", array(1 => $dir)),
657
        ), TRUE
totten's avatar
totten committed
658 659 660 661 662
      );
    }

    $configIDSiniDir = NULL;
    global $cmsPath;
colemanw's avatar
colemanw committed
663
    $siteDir = getSiteDir($cmsPath, $_SERVER['SCRIPT_FILENAME']);
totten's avatar
totten committed
664 665 666 667 668 669 670 671 672 673 674 675 676
    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,
      );
    }
677 678 679 680 681 682 683 684 685
    elseif ($installType == 'backdrop') {

      // make sure that we can write to sites/default and files/
      $writableDirectories = array(
        $cmsPath . CIVICRM_DIRECTORY_SEPARATOR .
        'files',
        $cmsPath,
      );
    }
totten's avatar
totten committed
686
    elseif ($installType == 'wordpress') {
687 688 689 690 691 692 693
      // 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);
totten's avatar
totten committed
694 695 696 697
    }

    foreach ($writableDirectories as $dir) {
      $dirName = CIVICRM_WINDOWS ? $dir : CIVICRM_DIRECTORY_SEPARATOR . $dir;
698 699 700 701
      $testDetails = array(
        ts("File permissions"),
        ts("Is the %1 folder writeable?", array(1 => $dir)),
        NULL,
totten's avatar
totten committed
702
      );
703
      $this->requireWriteable($dirName, $testDetails, TRUE);
totten's avatar
totten committed
704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724
    }

    //check for Config.IDS.ini, file may exist in re-install
    $configIDSiniDir = array($cmsPath, 'sites', $siteDir, 'files', 'civicrm', 'upload', 'Config.IDS.ini');

    if (is_array($configIDSiniDir) && !empty($configIDSiniDir)) {
      $configIDSiniFile = implode(CIVICRM_DIRECTORY_SEPARATOR, $configIDSiniDir);
      if (file_exists($configIDSiniFile)) {
        unlink($configIDSiniFile);
      }
    }

    // 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 == '') {
725
      $webserver = ts("I can't tell what webserver you are running");
totten's avatar
totten committed
726 727 728
    }

    // Check for $_SERVER configuration
729
    $this->requireServerVariables(array('SCRIPT_NAME', 'HTTP_HOST', 'SCRIPT_FILENAME'), array(
730 731 732
      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."),
733
    ));
totten's avatar
totten committed
734 735

    // Check for MySQL support
736
    $this->requireFunction('mysqli_connect', array(
737 738 739 740
      ts("PHP Configuration"),
      ts("MySQL support"),
      ts("MySQL support not included in PHP."),
    ));
totten's avatar
totten committed
741

742 743 744
    // Check for XML support
    $this->requireFunction('simplexml_load_file', array(
      ts("PHP Configuration"),
745 746
      ts("SimpleXML support"),
      ts("SimpleXML support not included in PHP."),
747 748
    ));

totten's avatar
totten committed
749
    // Check for JSON support
750 751 752 753 754
    $this->requireFunction('json_encode', array(
      ts("PHP Configuration"),
      ts("JSON support"),
      ts("JSON support not included in PHP."),
    ));
totten's avatar
totten committed
755

756 757 758 759 760 761 762
    // 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."),
    ));

totten's avatar
totten committed
763 764
    // Check for xcache_isset and emit warning if exists
    $this->checkXCache(array(
765 766 767
      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."),
768
    ));
totten's avatar
totten committed
769 770 771 772 773

    // Check memory allocation
    $this->requireMemory(32 * 1024 * 1024,
      64 * 1024 * 1024,
      array(
774 775 776
        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)),
totten's avatar
totten committed
777 778 779 780 781 782 783
        ini_get("memory_limit"),
      )
    );

    return $this->errors;
  }

784 785 786 787 788
  /**
   * @param $min
   * @param $recommended
   * @param $testDetails
   */
colemanw's avatar
colemanw committed
789
  public function requireMemory($min, $recommended, $testDetails) {
totten's avatar
totten committed
790 791 792 793
    $this->testing($testDetails);
    $mem = $this->getPHPMemory();

    if ($mem < $min && $mem > 0) {
794
      $testDetails[2] .= " " . ts("You only have %1 allocated", array(1 => ini_get("memory_limit")));
totten's avatar
totten committed
795 796 797
      $this->error($testDetails);
    }
    elseif ($mem < $recommended && $mem > 0) {
798
      $testDetails[2] .= " " . ts("You only have %1 allocated", array(1 => ini_get("memory_limit")));
totten's avatar
totten committed
799 800 801
      $this->warning($testDetails);
    }
    elseif ($mem == 0) {
802
      $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));
totten's avatar
totten committed
803 804 805 806
      $this->warning($testDetails);
    }
  }

807 808 809
  /**
   * @return float
   */
colemanw's avatar
colemanw committed
810
  public function getPHPMemory() {
totten's avatar
totten committed
811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827
    $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);
    }
  }

colemanw's avatar
colemanw committed
828
  public function listErrors() {
totten's avatar
totten committed
829
    if ($this->errors) {
830
      echo "<p>" . ts("The following problems are preventing me from installing CiviCRM:") . "</p>";
totten's avatar
totten committed
831 832 833 834 835 836
      foreach ($this->errors as $error) {
        echo "<li>" . htmlentities($error) . "</li>";
      }
    }
  }

837 838 839
  /**
   * @param null $section
   */
colemanw's avatar
colemanw committed
840
  public function showTable($section = NULL) {
totten's avatar
totten committed
841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861
    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>";
      }
    }
  }

862
  /**
colemanw's avatar
colemanw committed
863
   * @param string $funcName
864 865 866 867
   * @param $testDetails
   *
   * @return bool
   */
colemanw's avatar
colemanw committed
868
  public function requireFunction($funcName, $testDetails) {
totten's avatar
totten committed
869 870 871 872 873 874 875 876 877 878 879
    $this->testing($testDetails);

    if (!function_exists($funcName)) {
      $this->error($testDetails);
      return FALSE;
    }
    else {
      return TRUE;
    }
  }

880 881 882
  /**
   * @param $testDetails
   */
colemanw's avatar
colemanw committed
883
  public function checkXCache($testDetails) {
totten's avatar
totten committed
884 885 886 887 888 889 890 891
    if (function_exists('xcache_isset') &&
      ini_get('xcache.size') > 0
    ) {
      $this->testing($testDetails);
      $this->warning($testDetails);
    }
  }

892
  /**
893 894
   * @param array $testDetails
   * @return bool
895
   */
896
  public function requirePHPVersion($testDetails) {
totten's avatar
totten committed
897 898 899

    $this->testing($testDetails);

900
    $phpVersion = phpversion();
901
    $aboveMinVersion = version_compare($phpVersion, CRM_Upgrade_Incremental_General::MIN_INSTALL_PHP_VER) >= 0;
totten's avatar
totten committed
902

903
    if ($aboveMinVersion) {
904
      if (version_compare($phpVersion, CRM_Upgrade_Incremental_General::MIN_RECOMMENDED_PHP_VER) < 0) {
905
        $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(
906
          1 => $phpVersion,
907
          2 => CRM_Upgrade_Incremental_General::MIN_RECOMMENDED_PHP_VER,
908
          3 => CRM_Upgrade_Incremental_General::RECOMMENDED_PHP_VER,
909 910 911
        ));
        $this->warning($testDetails);
      }
totten's avatar
totten committed
912 913 914
      return TRUE;
    }

915 916
    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));
totten's avatar
totten committed
917 918 919 920 921
    }

    $this->error($testDetails);
  }

922
  /**
colemanw's avatar
colemanw committed
923
   * @param string $filename
924 925 926
   * @param $testDetails
   * @param bool $absolute
   */
colemanw's avatar
colemanw committed
927
  public function requireFile($filename, $testDetails, $absolute = FALSE) {
totten's avatar
totten committed
928 929 930 931 932
    $this->testing($testDetails);
    if (!$absolute) {
      $filename = $this->getBaseDir() . $filename;
    }
    if (!file_exists($filename)) {
933
      $testDetails[2] .= " (" . ts("file '%1' not found", array(1 => $filename)) . ')';
totten's avatar
totten committed
934 935 936 937
      $this->error($testDetails);
    }
  }

938 939 940
  /**
   * @param $testDetails
   */
colemanw's avatar
colemanw committed
941
  public function requireNoPathSeparator($testDetails) {
totten's avatar
totten committed
942 943 944 945 946 947
    $this->testing($testDetails);
    if (substr_count($this->getBaseDir(), PATH_SEPARATOR)) {
      $this->error($testDetails);
    }
  }

948
  /**
colemanw's avatar
colemanw committed
949
   * @param string $filename
950 951
   * @param $testDetails
   */
colemanw's avatar
colemanw committed
952
  public function requireNoFile($filename, $testDetails) {
totten's avatar
totten committed
953 954 955
    $this->testing($testDetails);
    $filename = $this->getBaseDir() . $filename;
    if (file_exists($filename)) {
956
      $testDetails[2] .= " (" . ts("file '%1' found", array(1 => $filename)) . ")";
totten's avatar
totten committed
957 958 959 960
      $this->error($testDetails);
    }
  }

961
  /**
colemanw's avatar
colemanw committed
962
   * @param string $filename
963 964
   * @param $testDetails
   */
colemanw's avatar
colemanw committed
965
  public function moveFileOutOfTheWay($filename, $testDetails) {
totten's avatar
totten committed
966 967 968 969 970 971 972 973 974 975
    $this->testing($testDetails);
    $filename = $this->getBaseDir() . $filename;
    if (file_exists($filename)) {
      if (file_exists("$filename.bak")) {
        rm("$filename.bak");
      }
      rename($filename, "$filename.bak");
    }
  }

976
  /**
colemanw's avatar
colemanw committed
977
   * @param string $filename
978 979 980
   * @param $testDetails
   * @param bool $absolute
   */
colemanw's avatar
colemanw committed
981
  public function requireWriteable($filename, $testDetails, $absolute = FALSE) {
totten's avatar
totten committed
982 983 984 985 986
    $this->testing($testDetails);
    if (!$absolute) {
      $filename = $this->getBaseDir() . $filename;
    }

987
    if (!is_writable($filename)) {
totten's avatar
totten committed
988 989 990 991 992 993 994 995 996
      $name = NULL;
      if (function_exists('posix_getpwuid')) {
        $user = posix_getpwuid(posix_geteuid());
        $name = '- ' . $user['name'] . ' -';
      }

      if (!isset($testDetails[2])) {
        $testDetails[2] = NULL;
      }
997
      $testDetails[2] .= ts("The user account used by your web-server %1 needs to be granted write access to the following directory in order to configure the CiviCRM settings file:", array(1 => $name)) . "\n$filename";
totten's avatar
totten committed
998 999 1000 1001
      $this->error($testDetails);
    }
  }

1002
  /**
colemanw's avatar
colemanw committed
1003
   * @param string $moduleName
1004 1005
   * @param $testDetails
   */
colemanw's avatar
colemanw committed
1006
  public function requireApacheModule($moduleName, $testDetails) {
totten's avatar
totten committed
1007 1008 1009 1010 1011 1012
    $this->testing($testDetails);
    if (!in_array($moduleName, apache_get_modules())) {
      $this->error($testDetails);
    }
  }

1013 1014
  /**
   * @param $server
colemanw's avatar
colemanw committed
1015
   * @param string $username
1016 1017 1018
   * @param $password
   * @param $testDetails
   */
colemanw's avatar
colemanw committed
1019
  public function requireMysqlConnection($server, $username, $password, $testDetails) {
totten's avatar
totten committed
1020
    $this->testing($testDetails);
1021
    $this->conn = $this->connect($server, $username, $password);
totten's avatar
totten committed
1022

1023
    if ($this->conn) {
totten's avatar
totten committed
1024 1025 1026
      return TRUE;
    }
    else {
1027
      $testDetails[2] .= ": " . mysqli_connect_error();
totten's avatar
totten committed
1028 1029 1030 1031
      $this->error($testDetails);
    }
  }

1032 1033 1034 1035
  /**
   * @param $server
   * @param $testDetails
   */
colemanw's avatar
colemanw committed
1036
  public function requireMySQLServer($server, $testDetails) {
totten's avatar
totten committed
1037
    $this->testing($testDetails);
1038
    $conn = $this->connect($server, NULL, NULL);
totten's avatar
totten committed
1039

1040
    if ($conn || mysqli_connect_errno() < 2000) {
totten's avatar
totten committed
1041 1042 1043
      return TRUE;
    }
    else {
1044
      $testDetails[2] .= ": " . mysqli_connect_error();
totten's avatar
totten committed
1045 1046 1047 1048
      $this->error($testDetails);
    }
  }

1049 1050 1051 1052
  /**
   * @param $version
   * @param $testDetails
   */
colemanw's avatar
colemanw committed
1053
  public function requireMySQLVersion($version, $testDetails) {
totten's avatar
totten committed
1054 1055
    $this->testing($testDetails);

1056
    if (!mysqli_get_server_info($this->conn)) {
1057
      $testDetails[2] = ts('Cannot determine the version of MySQL installed. Please ensure at least version %1 is installed.', array(1 => $version));
totten's avatar
totten committed
1058 1059 1060 1061
      $this->warning($testDetails);
    }
    else {
      list($majorRequested, $minorRequested) = explode('.', $version);
1062
      list($majorHas, $minorHas) = explode('.', mysqli_get_server_info($this->conn));
totten's avatar
totten committed
1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073

      if (($majorHas > $majorRequested) || ($majorHas == $majorRequested && $minorHas >= $minorRequested)) {
        return TRUE;
      }
      else {
        $testDetails[2] .= "{$majorHas}.{$minorHas}.";
        $this->error($testDetails);
      }
    }
  }

1074 1075
  /**
   * @param $server
colemanw's avatar
colemanw committed
1076
   * @param string $username
1077 1078 1079 1080
   * @param $password
   * @param $database
   * @param $testDetails
   */
colemanw's avatar
colemanw committed
1081
  public function requireMySQLInnoDB($server, $username, $password, $database, $testDetails) {
totten's avatar
totten committed
1082
    $this->testing($testDetails);
1083
    $conn = $this->connect($server, $username, $password);
totten's avatar
totten committed
1084
    if (!$conn) {
1085
      $testDetails[2] .= ' ' . ts("Could not determine if MySQL has InnoDB support. Assuming no.");
totten's avatar
totten committed
1086 1087 1088 1089 1090
      $this->error($testDetails);
      return;
    }

    $innodb_support = FALSE;
1091 1092
    $result = mysqli_query($conn, "SHOW ENGINES");
    while ($values = mysqli_fetch_array($result)) {
totten's avatar
totten committed
1093 1094 1095 1096 1097 1098 1099 1100 1101
      if ($values['Engine'] == 'InnoDB') {
        if (strtolower($values['Support']) == 'yes' ||
          strtolower($values['Support']) == 'default'
        ) {
          $innodb_support = TRUE;
        }
      }
    }
    if ($innodb_support) {
1102
      $testDetails[3] = ts('MySQL server does have InnoDB support');
totten's avatar
totten committed
1103 1104
    }
    else {
1105
      $testDetails[2] .= ' ' . ts('Could not determine if MySQL has InnoDB support. Assuming no');
totten's avatar
totten committed
1106 1107 1108
    }
  }

1109 1110
  /**
   * @param $server
colemanw's avatar
colemanw committed
1111
   * @param string $username
1112 1113 1114 1115
   * @param $password
   * @param $database
   * @param $testDetails
   */
colemanw's avatar
colemanw committed
1116
  public function requireMySQLTempTables($server, $username, $password, $database, $testDetails) {
totten's avatar
totten committed
1117
    $this->testing($testDetails);
1118
    $conn = $this->connect($server, $username, $password);
totten's avatar
totten committed
1119
    if (!$conn) {
1120
      $testDetails[2] = ts('Could not login to the database.');
totten's avatar
totten committed
1121 1122 1123 1124
      $this->error($testDetails);
      return;
    }

1125
    if (!@mysqli_select_db($conn, $database)) {
1126
      $testDetails[2] = ts('Could not select the database.');
totten's avatar
totten committed
1127 1128 1129 1130
      $this->error($testDetails);
      return;
    }

1131
    $result = mysqli_query($conn, 'CREATE TEMPORARY TABLE civicrm_install_temp_table_test (test text)');
totten's avatar
totten committed
1132
    if (!$result) {
1133
      $testDetails[2] = ts('Could not create a temp table.');
totten's avatar
totten committed
1134 1135
      $this->error($testDetails);
    }
1136
    $result = mysqli_query($conn, 'DROP TEMPORARY TABLE civicrm_install_temp_table_test');
totten's avatar
totten committed
1137 1138
  }

1139 1140
  /**
   * @param $server
colemanw's avatar
colemanw committed
1141
   * @param string $username
1142 1143 1144 1145
   * @param $password
   * @param $database
   * @param $testDetails
   */
colemanw's avatar
colemanw committed
1146
  public function requireMySQLTrigger($server, $username, $password, $database, $testDetails) {
totten's avatar
totten committed
1147
    $this->testing($testDetails);
1148
    $conn = $this->connect($server, $username, $password);
totten's avatar
totten committed
1149
    if (!$conn) {
1150
      $testDetails[2] = ts('Could not login to the database.');
totten's avatar
totten committed
1151 1152 1153 1154
      $this->error($testDetails);
      return;
    }

1155
    if (!@mysqli_select_db($conn, $database)) {
1156
      $testDetails[2] = ts('Could not select the database.');
totten's avatar
totten committed
1157 1158 1159 1160
      $this->error($testDetails);
      return;
    }

1161
    $result = mysqli_query($conn, 'CREATE TABLE civicrm_install_temp_table_test (test text)');
totten's avatar
totten committed
1162
    if (!$result) {
1163
      $testDetails[2] = ts('Could not create a table in the database.');
totten's avatar
totten committed
1164 1165 1166
      $this->error($testDetails);
    }

1167
    $result = mysqli_query($conn, 'CREATE TRIGGER civicrm_install_temp_table_test_trigger BEFORE INSERT ON civicrm_install_temp_table_test FOR EACH ROW BEGIN END');
totten's avatar
totten committed
1168
    if (!$result) {
1169
      mysqli_query($conn, 'DROP TABLE civicrm_install_temp_table_test');
1170
      $testDetails[2] = ts('Could not create a database trigger.');
totten's avatar
totten committed
1171 1172 1173
      $this->error($testDetails);
    }

1174 1175
    mysqli_query($conn, 'DROP TRIGGER civicrm_install_temp_table_test_trigger');
    mysqli_query($conn, 'DROP TABLE civicrm_install_temp_table_test');
totten's avatar
totten committed
1176 1177
  }

1178 1179
  /**
   * @param $server
colemanw's avatar
colemanw committed
1180
   * @param string $username
1181 1182 1183 1184
   * @param $password
   * @param $database
   * @param $testDetails
   */
colemanw's avatar
colemanw committed
1185
  public function requireMySQLLockTables($server, $username, $password, $database, $testDetails) {
totten's avatar
totten committed
1186
    $this->testing($testDetails);
1187
    $conn = $this->connect($server, $username, $password);
totten's avatar
totten committed
1188
    if (!$conn) {
1189
      $testDetails[2] = ts('Could not connect to the database server.');
totten's avatar
totten committed
1190 1191 1192 1193
      $this->error($testDetails);
      return;
    }

1194
    if (!@mysqli_select_db($conn, $database)) {
1195
      $testDetails[2] = ts('Could not select the database.');
totten's avatar
totten committed
1196 1197 1198 1199
      $this->error($testDetails);
      return;
    }

1200
    $result = mysqli_query($conn, 'CREATE TEMPORARY TABLE civicrm_install_temp_table_test (test text)');
totten's avatar
totten committed
1201
    if (!$result) {
1202
      $testDetails[2] = ts('Could not create a table in the database.');
totten's avatar
totten committed
1203 1204 1205 1206
      $this->error($testDetails);
      return;
    }

1207
    $result = mysqli_query($conn, 'LOCK TABLES civicrm_install_temp_table_test WRITE');
totten's avatar
totten committed
1208
    if (!$result) {
1209
      $testDetails[2] = ts('Could not obtain a write lock for the database table.');
totten's avatar
totten committed
1210
      $this->error($testDetails);
1211
      $result = mysqli_query($conn, 'DROP TEMPORARY TABLE civicrm_install_temp_table_test');
totten's avatar
totten committed
1212 1213 1214
      return;
    }

1215
    $result = mysqli_query($conn, 'UNLOCK TABLES');
totten's avatar
totten committed
1216
    if (!$result) {
1217
      $testDetails[2] = ts('Could not release the lock for the database table.');
totten's avatar
totten committed
1218
      $this->error($testDetails);
1219
      $result = mysqli_query($conn, 'DROP TEMPORARY TABLE civicrm_install_temp_table_test');
totten's avatar
totten committed
1220 1221 1222
      return;
    }

1223
    $result = mysqli_query($conn, 'DROP TEMPORARY TABLE civicrm_install_temp_table_test');
totten's avatar
totten committed
1224 1225
  }

1226 1227
  /**
   * @param $server
colemanw's avatar
colemanw committed
1228
   * @param string $username
1229 1230 1231
   * @param $password
   * @param $testDetails
   */
colemanw's avatar
colemanw committed
1232
  public function requireMySQLAutoIncrementIncrementOne($server, $username, $password, $testDetails) {
totten's avatar
totten committed
1233
    $this->testing($testDetails);
1234
    $conn = $this->connect($server, $username, $password);
totten's avatar
totten committed
1235
    if (!$conn) {
1236
      $testDetails[2] = ts('Could not connect to the database server.');
totten's avatar
totten committed
1237 1238 1239 1240
      $this->error($testDetails);
      return;
    }

1241
    $result = mysqli_query($conn, "SHOW variables like 'auto_increment_increment'");
totten's avatar
totten committed
1242
    if (!$result) {
1243
      $testDetails[2] = ts('Could not query database server variables.');
totten's avatar
totten committed
1244 1245 1246 1247
      $this->error($testDetails);
      return;
    }
    else {
1248
      $values = mysqli_fetch_row($result);
totten's avatar
totten committed
1249
      if ($values[1] == 1) {
1250
        $testDetails[3] = ts('MySQL server auto_increment_increment is 1');
totten's avatar
totten committed
1251 1252 1253 1254 1255 1256 1257
      }
      else {
        $this->error($testDetails);
      }
    }
  }

1258 1259
  /**
   * @param $server
colemanw's avatar
colemanw committed
1260
   * @param string $username
1261 1262 1263 1264 1265
   * @param $password
   * @param $database
   * @param $minValueKB
   * @param $testDetails
   */
colemanw's avatar
colemanw committed
1266
  public function requireMySQLThreadStack($server, $username, $password, $database, $minValueKB, $testDetails) {
totten's avatar
totten committed
1267
    $this->testing($testDetails);
1268
    $conn = $this->connect($server, $username, $password);
totten's avatar
totten committed
1269
    if (!$conn) {
1270
      $testDetails[2] = ts('Could not connect to the database server.');
totten's avatar
totten committed
1271 1272 1273 1274
      $this->error($testDetails);
      return;
    }

1275
    if (!@mysqli_select_db($conn, $database)) {
1276
      $testDetails[2] = ts('Could not select the database.');
totten's avatar
totten committed
1277 1278 1279 1280
      $this->error($testDetails);
      return;
    }

1281 1282
    // bytes => kb
    $result = mysqli_query($conn, "SHOW VARIABLES LIKE 'thread_stack'");
totten's avatar
totten committed
1283
    if (!$result) {
1284
      $testDetails[2] = ts('Could not get information about the thread_stack of the database.');
totten's avatar
totten committed
1285
      $this->error($testDetails);
1286 1287
    }
    else {
1288
      $values = mysqli_fetch_row($result);
totten's avatar
totten committed
1289
      if ($values[1] < (1024 * $minValueKB)) {
1290
        $testDetails[2] = ts('MySQL "thread_stack" is %1 kb', array(1 => ($values[1] / 1024)));
totten's avatar
totten committed
1291 1292 1293 1294 1295
        $this->error($testDetails);
      }
    }
  }

1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322
  /**
   * @param $server
   * @param $username
   * @param $password
   * @param $database
   * @param $testDetails
   */
  public function requireNoExistingData(
    $server,
    $username,
    $password,
    $database,
    $testDetails
  ) {
    $this->testing($testDetails);
    $conn = $this->connect($server, $username, $password);

    @mysqli_select_db($conn, $database);
    $contactRecords = mysqli_query($conn, "SELECT count(*) as contactscount FROM civicrm_contact");
    if ($contactRecords) {
      $contactRecords = mysqli_fetch_object($contactRecords);
      if ($contactRecords->contactscount > 0) {
        $this->error($testDetails);
        return;
      }
    }

eileen's avatar
eileen committed
1323
    $testDetails[3] = ts('CiviCRM data from previous installation does not exist in %1.', array(1 => $database));
1324 1325 1326
    $this->testing($testDetails);
  }

1327 1328
  /**
   * @param $server
colemanw's avatar
colemanw committed
1329
   * @param string $username
1330 1331 1332 1333 1334
   * @param $password
   * @param $database
   * @param $testDetails
   * @param bool $onlyRequire
   */
colemanw's avatar
colemanw committed
1335
  public function requireDatabaseOrCreatePermissions(
1336
    $server,
totten's avatar
totten committed
1337 1338 1339 1340 1341 1342 1343
    $username,
    $password,
    $database,
    $testDetails,
    $onlyRequire = FALSE
  ) {
    $this->testing($testDetails);
1344
    $conn = $this->connect($server, $username, $password);
totten's avatar
totten committed
1345 1346

    $okay = NULL;
1347
    if (@mysqli_select_db($conn, $database)) {
totten's avatar
totten committed
1348 1349 1350
      $okay = "Database '$database' exists";
    }
    elseif ($onlyRequire) {
1351
      $testDetails[2] = ts("The database: '%1' does not exist.", array(1 => $database));
totten's avatar
totten committed
1352 1353 1354 1355
      $this->error($testDetails);
      return;
    }
    else {
1356 1357
      $query = sprintf("CREATE DATABASE %s", mysqli_real_escape_string($conn, $database));
      if (@mysqli_query($conn, $query)) {
1358
        $okay = ts("Able to create a new database.");
totten's avatar
totten committed
1359 1360
      }
      else {
1361
        $testDetails[2] .= " (" . ts("user '%1' doesn't have CREATE DATABASE permissions.", array(1 => $username)) . ")";
totten's avatar
totten committed
1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372
        $this->error($testDetails);
        return;
      }
    }

    if ($okay) {
      $testDetails[3] = $okay;
      $this->testing($testDetails);
    }
  }

1373 1374 1375 1376
  /**
   * @param $varNames
   * @param $errorMessage
   */
colemanw's avatar
colemanw committed
1377
  public function requireServerVariables($varNames, $errorMessage) {
totten's avatar
totten committed
1378 1379 1380 1381 1382 1383 1384 1385 1386 1387
    //$this->testing($testDetails);
    foreach ($varNames as $varName) {
      if (!$_SERVER[$varName]) {
        $missing[] = '$_SERVER[' . $varName . ']';
      }
    }
    if (!isset($missing)) {
      return TRUE;
    }
    else {
1388
      $testDetails[2] = " (" . ts('the following PHP variables are missing: %1', array(1 => implode(", ", $missing))) . ")";
totten's avatar
totten committed
1389 1390 1391 1392
      $this->error($testDetails);
    }
  }

1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443
  /**
   * @param $server
   * @param string $username
   * @param $password
   * @param $database
   * @param $testDetails
   */
  public function requireMysqlUtf8mb4($server, $username, $password, $database, $testDetails) {
    $this->testing($testDetails);
    $conn = $this->connect($server, $username, $password);
    if (!$conn) {
      $testDetails[2] = ts('Could not connect to the database server.');
      $this->error($testDetails);
      return;
    }

    if (!@mysqli_select_db($conn, $database)) {
      $testDetails[2] = ts('Could not select the database.');
      $this->error($testDetails);
      return;
    }

    $result = mysqli_query($conn, 'CREATE TABLE civicrm_utf8mb4_test (id VARCHAR(255), PRIMARY KEY(id(255))) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC ENGINE=INNODB');
    if (!$result) {
      $testDetails[2] = ts('It is recommended, though not yet required, to configure your MySQL server for utf8mb4 support. You will need the following MySQL server configuration: innodb_large_prefix=true innodb_file_format=barracuda innodb_file_per_table=true');
      $this->warning($testDetails);
      return;
    }
    $result = mysqli_query($conn, 'DROP TABLE civicrm_utf8mb4_test');

    // Ensure that the MySQL driver supports utf8mb4 encoding.
    $version = mysqli_get_client_info($conn);
    if (strpos($version, 'mysqlnd') !== FALSE) {
      // The mysqlnd driver supports utf8mb4 starting at version 5.0.9.
      $version = preg_replace('/^\D+([\d.]+).*/', '$1', $version);
      if (version_compare($version, '5.0.9', '<')) {
        $testDetails[2] = 'It is recommended, though not yet required, to upgrade your PHP MySQL driver (mysqlnd) to >= 5.0.9 for utf8mb4 support.';
        $this->warning($testDetails);
        return;
      }
    }
    else {
      // The libmysqlclient driver supports utf8mb4 starting at version 5.5.3.
      if (version_compare($version, '5.5.3', '<')) {
        $testDetails[2] = 'It is recommended, though not yet required, to upgrade your PHP MySQL driver (libmysqlclient) to >= 5.5.3 for utf8mb4 support.';
        $this->warning($testDetails);
        return;
      }
    }
  }