4 * Note that this installer has been based of the SilverStripe installer.
5 * You can get more information from the SilverStripe Website at
6 * http://www.silverstripe.com/.
8 * Copyright (c) 2006-7, SilverStripe Limited - www.silverstripe.com
11 * License: BSD-3-clause
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions are
16 * Redistributions of source code must retain the above copyright notice,
17 * this list of conditions and the following disclaimer.
19 * Redistributions in binary form must reproduce the above copyright
20 * notice, this list of conditions and the following disclaimer in the
21 * documentation and/or other materials provided with the distribution.
23 * Neither the name of SilverStripe nor the names of its contributors may
24 * be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
28 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
29 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
30 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
31 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
32 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
33 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
34 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
35 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
36 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
37 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 * Changes and modifications (c) 2007-2017 by CiviCRM LLC
46 ini_set('max_execution_time', 3000);
48 if (stristr(PHP_OS
, 'WIN')) {
49 define('CIVICRM_DIRECTORY_SEPARATOR', '/');
50 define('CIVICRM_WINDOWS', 1);
53 define('CIVICRM_DIRECTORY_SEPARATOR', DIRECTORY_SEPARATOR
);
54 define('CIVICRM_WINDOWS', 0);
60 global $installDirPath;
61 global $installURLPath;
63 // Set the install type
64 // this is sent as a query string when the page is first loaded
65 // and subsequently posted to the page as a hidden field
66 // only permit acceptable installation types to prevent issues;
67 $acceptableInstallTypes = ['drupal', 'wordpress', 'backdrop'];
68 if (isset($_POST['civicrm_install_type']) && in_array($_POST['civicrm_install_type'], $acceptableInstallTypes)) {
69 $installType = $_POST['civicrm_install_type'];
71 elseif (isset($_GET['civicrm_install_type']) && in_array(strtolower($_GET['civicrm_install_type']), $acceptableInstallTypes)) {
72 $installType = strtolower($_GET['civicrm_install_type']);
75 // default value if not set and not an acceptable install type.
76 $installType = "drupal";
79 if ($installType == 'drupal' ||
$installType == 'backdrop') {
80 $crmPath = dirname(dirname($_SERVER['SCRIPT_FILENAME']));
81 $installDirPath = $installURLPath = '';
83 elseif ($installType == 'wordpress') {
84 $crmPath = WP_PLUGIN_DIR
. DIRECTORY_SEPARATOR
. 'civicrm' . DIRECTORY_SEPARATOR
. 'civicrm' . DIRECTORY_SEPARATOR
;
85 $installDirPath = WP_PLUGIN_DIR
. DIRECTORY_SEPARATOR
. 'civicrm' . DIRECTORY_SEPARATOR
. 'civicrm' . DIRECTORY_SEPARATOR
. 'install' . DIRECTORY_SEPARATOR
;
86 $installURLPath = WP_PLUGIN_URL
. DIRECTORY_SEPARATOR
. 'civicrm' . DIRECTORY_SEPARATOR
. 'civicrm' . DIRECTORY_SEPARATOR
. 'install' . DIRECTORY_SEPARATOR
;
89 $errorTitle = "Oops! Unsupported installation mode";
90 $errorMsg = sprintf('%s: unknown installation mode. Please refer to the online documentation for more information.', $installType);
91 errorDisplayPage($errorTitle, $errorMsg, FALSE);
94 $composerJsonPath = dirname(__DIR__
) . DIRECTORY_SEPARATOR
. 'composer.json';
95 if (file_exists($composerJsonPath)) {
96 $composerJson = json_decode(file_get_contents($composerJsonPath), 1);
97 $minPhpVer = preg_replace(';[~^];', '', $composerJson['require']['php']);
98 if (!version_compare(phpversion(), $minPhpVer, '>=')) {
99 errorDisplayPage('PHP Version Requirement', sprintf("CiviCRM requires PHP %s+. The web server is running PHP %s.", $minPhpVer, phpversion()), FALSE);
103 $pkgPath = $crmPath . DIRECTORY_SEPARATOR
. 'packages';
105 require_once $crmPath . '/CRM/Core/ClassLoader.php';
106 CRM_Core_ClassLoader
::singleton()->register();
109 if (isset($_POST['loadGenerated'])) {
113 require_once dirname(__FILE__
) . CIVICRM_DIRECTORY_SEPARATOR
. 'langs.php';
114 foreach ($langs as $locale => $_) {
115 if ($locale == 'en_US') {
118 if (!file_exists(implode(CIVICRM_DIRECTORY_SEPARATOR
, array($crmPath, 'sql', "civicrm_data.$locale.mysql")))) {
119 unset($langs[$locale]);
124 // This is mostly sympbolic, since nothing we do during the install
125 // really requires CIVICRM_UF to be defined.
126 $installTypeToUF = array(
127 'wordpress' => 'WordPress',
128 'drupal' => 'Drupal',
129 'backdrop' => 'Backdrop',
132 $uf = ($installTypeToUF[$installType] ??
'Drupal');
133 define('CIVICRM_UF', $uf);
135 // Set the Locale (required by CRM_Core_Config)
139 $seedLanguage = 'en_US';
141 // Backwards compatibility with default location of l10n files
142 if (!defined('CIVICRM_L10N_BASEDIR') && file_exists($crmPath . DIRECTORY_SEPARATOR
. 'l10n')) {
143 define('CIVICRM_L10N_BASEDIR', $crmPath . DIRECTORY_SEPARATOR
. 'l10n');
146 // CRM-16801 This validates that seedLanguage is valid by looking in $langs.
147 // NB: the variable is initial a $_REQUEST for the initial page reload,
148 // then becomes a $_POST when the installation form is submitted.
149 if (isset($_REQUEST['seedLanguage']) and isset($langs[$_REQUEST['seedLanguage']])) {
150 $seedLanguage = $_REQUEST['seedLanguage'];
151 $tsLocale = $_REQUEST['seedLanguage'];
154 CRM_Core_Config
::singleton(FALSE);
155 $GLOBALS['civicrm_default_error_scope'] = NULL;
157 // The translation files are in the parent directory (l10n)
158 $i18n = CRM_Core_I18n
::singleton();
160 // Support for Arabic, Hebrew, Farsi, etc.
161 // Used in the template.html
162 $short_lang_code = CRM_Core_I18n_PseudoConstant
::shortForLong($tsLocale);
163 $text_direction = (CRM_Core_I18n
::isLanguageRTL($tsLocale) ?
'rtl' : 'ltr');
166 if ($installType == 'drupal') {
167 //CRM-6840 -don't force to install in sites/all/modules/
168 $object = new CRM_Utils_System_Drupal();
169 $cmsPath = $object->cmsRootPath();
171 $siteDir = getSiteDir($cmsPath, $_SERVER['SCRIPT_FILENAME']);
172 $alreadyInstalled = file_exists($cmsPath . CIVICRM_DIRECTORY_SEPARATOR
.
173 'sites' . CIVICRM_DIRECTORY_SEPARATOR
.
174 $siteDir . CIVICRM_DIRECTORY_SEPARATOR
.
175 'civicrm.settings.php'
178 elseif ($installType == 'backdrop') {
179 $object = new CRM_Utils_System_Backdrop();
180 $cmsPath = $object->cmsRootPath();
181 $siteDir = getSiteDir($cmsPath, $_SERVER['SCRIPT_FILENAME']);
182 $alreadyInstalled = file_exists($cmsPath . CIVICRM_DIRECTORY_SEPARATOR
. 'civicrm.settings.php');
184 elseif ($installType == 'wordpress') {
185 $cmsPath = WP_PLUGIN_DIR
. DIRECTORY_SEPARATOR
. 'civicrm';
186 $upload_dir = wp_upload_dir();
187 $files_dirname = $upload_dir['basedir'] . DIRECTORY_SEPARATOR
. 'civicrm';
188 $wp_civi_settings = $upload_dir['basedir'] . DIRECTORY_SEPARATOR
. 'civicrm' . DIRECTORY_SEPARATOR
. 'civicrm.settings.php';
189 $wp_civi_settings_deprectated = CIVICRM_PLUGIN_DIR
. 'civicrm.settings.php';
190 if (file_exists($wp_civi_settings_deprectated)) {
191 $alreadyInstalled = $wp_civi_settings_deprectated;
193 elseif (file_exists($wp_civi_settings)) {
194 $alreadyInstalled = $wp_civi_settings;
198 if ($installType == 'drupal') {
199 // Lets check only /modules/.
200 $pattern = '/' . preg_quote(CIVICRM_DIRECTORY_SEPARATOR
. 'modules', CIVICRM_DIRECTORY_SEPARATOR
) . '/';
202 if (!preg_match($pattern, str_replace("\\", "/", $_SERVER['SCRIPT_FILENAME']))) {
203 $directory = implode(CIVICRM_DIRECTORY_SEPARATOR
, array('sites', 'all', 'modules'));
204 $errorTitle = ts("Oops! Please correct your install location");
205 $errorMsg = ts("Please untar (uncompress) your downloaded copy of CiviCRM in the <strong>%1</strong> directory below your Drupal root directory.", array(1 => $directory));
206 errorDisplayPage($errorTitle, $errorMsg);
210 if ($installType == 'backdrop') {
211 // Lets check only /modules/.
212 $pattern = '/' . preg_quote(CIVICRM_DIRECTORY_SEPARATOR
. 'modules', CIVICRM_DIRECTORY_SEPARATOR
) . '/';
214 if (!preg_match($pattern, str_replace("\\", "/", $_SERVER['SCRIPT_FILENAME']))) {
215 $directory = 'modules';
216 $errorTitle = ts("Oops! Please correct your install location");
217 $errorMsg = ts("Please untar (uncompress) your downloaded copy of CiviCRM in the <strong>%1</strong> directory below your Drupal root directory.", array(1 => $directory));
218 errorDisplayPage($errorTitle, $errorMsg);
222 // Exit with error if CiviCRM has already been installed.
223 if ($alreadyInstalled) {
224 $errorTitle = ts("Oops! CiviCRM is already installed");
225 $settings_directory = $cmsPath;
227 if ($installType == 'drupal') {
228 $settings_directory = implode(CIVICRM_DIRECTORY_SEPARATOR
, array(
229 ts('[your Drupal root directory]'),
234 if ($installType == 'backdrop') {
235 $settings_directory = implode(CIVICRM_DIRECTORY_SEPARATOR
, array(
236 ts('[your Backdrop root directory]'),
241 $docLink = CRM_Utils_System
::docURL2('Installation and Upgrades', FALSE, ts('Installation Guide'), NULL, NULL, "wiki");
242 $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));
243 errorDisplayPage($errorTitle, $errorMsg, FALSE);
246 $versionFile = $crmPath . CIVICRM_DIRECTORY_SEPARATOR
. 'civicrm-version.php';
247 if (file_exists($versionFile)) {
248 require_once $versionFile;
249 $civicrm_version = civicrmVersion();
252 $civicrm_version = 'unknown';
255 if ($installType == 'drupal') {
256 // Ensure that they have downloaded the correct version of CiviCRM
257 if ($civicrm_version['cms'] != 'Drupal' && $civicrm_version['cms'] != 'Drupal6') {
258 $errorTitle = ts("Oops! Incorrect CiviCRM version");
259 $errorMsg = ts("This installer can only be used for the Drupal version of CiviCRM.");
260 errorDisplayPage($errorTitle, $errorMsg);
263 define('DRUPAL_ROOT', $cmsPath);
264 $drupalVersionFiles = array(
266 implode(CIVICRM_DIRECTORY_SEPARATOR
, array($cmsPath, 'modules', 'system', 'system.module')),
268 implode(CIVICRM_DIRECTORY_SEPARATOR
, array($cmsPath, 'includes', 'bootstrap.inc')),
270 foreach ($drupalVersionFiles as $drupalVersionFile) {
271 if (file_exists($drupalVersionFile)) {
272 require_once $drupalVersionFile;
276 // Bootstrap Drupal to get settings and user
277 $base_root = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ?
'https' : 'http';
278 $base_root .= '://' . $_SERVER['HTTP_HOST'];
279 $base_url = $base_root;
280 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL
);
282 // Check that user is logged in and has administrative permissions
283 // This is necessary because the script exposes the database settings in the form and these could be viewed by unauthorised users
284 if ((!function_exists('user_access')) ||
(!user_access('administer site configuration'))) {
285 $errorTitle = ts("You don't have permission to access this page");
286 $errorMsg = ts("The installer can only be run by a user with the permission to administer site configuration.");
287 errorDisplayPage($errorTitle, $errorMsg);
291 if (!defined('VERSION') or version_compare(VERSION
, '6.0') < 0) {
292 $errorTitle = ts("Oops! Incorrect Drupal version");
293 $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)));
294 errorDisplayPage($errorTitle, $errorMsg);
297 elseif ($installType == 'backdrop') {
298 // Ensure that they have downloaded the correct version of CiviCRM
299 if ($civicrm_version['cms'] != 'Backdrop') {
300 $errorTitle = ts("Oops! Incorrect CiviCRM version");
301 $errorMsg = ts("This installer can only be used for the Backdrop version of CiviCRM.");
302 errorDisplayPage($errorTitle, $errorMsg);
305 define('BACKDROP_ROOT', $cmsPath);
307 $backdropVersionFiles = array(
309 implode(CIVICRM_DIRECTORY_SEPARATOR
, array($cmsPath, 'core', 'includes', 'bootstrap.inc')),
311 foreach ($backdropVersionFiles as $backdropVersionFile) {
312 if (file_exists($backdropVersionFile)) {
313 require_once $backdropVersionFile;
316 if (!defined('BACKDROP_VERSION') or version_compare(BACKDROP_VERSION
, '1.0') < 0) {
317 $errorTitle = ts("Oops! Incorrect Backdrop version");
318 $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)));
319 errorDisplayPage($errorTitle, $errorMsg);
322 elseif ($installType == 'wordpress') {
324 $civicrm_version['cms'] = 'WordPress';
326 // Ensure that they have downloaded the correct version of CiviCRM
327 if ($civicrm_version['cms'] != 'WordPress') {
328 $errorTitle = ts("Oops! Incorrect CiviCRM version");
329 $errorMsg = ts("This installer can only be used for the WordPress version of CiviCRM.");
330 errorDisplayPage($errorTitle, $errorMsg);
334 // Load CiviCRM database config
335 if (isset($_POST['mysql'])) {
336 $databaseConfig = $_POST['mysql'];
339 if ($installType == 'wordpress') {
340 // Load WP database config
341 if (isset($_POST['mysql'])) {
342 $databaseConfig = $_POST['mysql'];
345 $databaseConfig = array(
347 "username" => DB_USER
,
348 "password" => DB_PASSWORD
,
349 "database" => DB_NAME
,
354 if ($installType == 'drupal') {
355 // Load drupal database config
356 if (isset($_POST['drupal'])) {
357 $drupalConfig = $_POST['drupal'];
360 $dbServer = $databases['default']['default']['host'];
361 if (!empty($databases['default']['default']['port'])) {
362 $dbServer .= ':' . $databases['default']['default']['port'];
364 $drupalConfig = array(
365 "server" => $dbServer,
366 "username" => $databases['default']['default']['username'],
367 "password" => $databases['default']['default']['password'],
368 "database" => $databases['default']['default']['database'],
373 if ($installType == 'backdrop') {
374 // Load backdrop database config
375 if (isset($_POST['backdrop'])) {
376 $backdropConfig = $_POST['backdrop'];
379 $backdropConfig = array(
380 "server" => "localhost",
381 "username" => "backdrop",
383 "database" => "backdrop",
388 // By default set CiviCRM database to be same as CMS database
389 if (!isset($databaseConfig)) {
390 if (($installType == 'drupal') && (isset($drupalConfig))) {
391 $databaseConfig = $drupalConfig;
393 if (($installType == 'backdrop') && (isset($backdropConfig))) {
394 $databaseConfig = $backdropConfig;
398 // Check requirements
399 $req = new InstallRequirements();
402 if ($req->hasErrors()) {
403 $hasErrorOtherThanDatabase = TRUE;
406 if ($databaseConfig) {
407 $dbReq = new InstallRequirements();
408 $dbReq->checkdatabase($databaseConfig, 'CiviCRM');
409 if ($installType == 'drupal') {
410 $dbReq->checkdatabase($drupalConfig, 'Drupal');
412 if ($installType == 'backdrop') {
413 $dbReq->checkdatabase($backdropConfig, 'Backdrop');
418 if (isset($_POST['go']) && !$req->hasErrors() && !$dbReq->hasErrors()) {
419 // Confirm before reinstalling
420 if (!isset($_POST['force_reinstall']) && $alreadyInstalled) {
421 include $installDirPath . 'template.html';
424 $inst = new Installer();
425 $inst->install($_POST);
428 // Show the config form
431 include $installDirPath . 'template.html';
435 * This class checks requirements
436 * Each of the requireXXX functions takes an argument which gives a user description of the test. It's an array
438 * $description[0] - The test category
439 * $description[1] - The test title
440 * $description[2] - The test error to show, if it goes wrong
442 class InstallRequirements
{
448 // @see CRM_Upgrade_Form::MINIMUM_THREAD_STACK
449 const MINIMUM_THREAD_STACK
= 192;
452 * Just check that the database configuration is okay.
453 * @param $databaseConfig
456 public function checkdatabase($databaseConfig, $dbName) {
457 if ($this->requireFunction('mysqli_connect',
459 ts("PHP Configuration"),
461 ts("MySQL support not included in PHP."),
465 $this->requireMySQLServer($databaseConfig['server'],
467 ts("MySQL %1 Configuration", array(1 => $dbName)),
468 ts("Does the server exist?"),
469 ts("Can't find the a MySQL server on '%1'.", array(1 => $databaseConfig['server'])),
470 $databaseConfig['server'],
473 if ($this->requireMysqlConnection($databaseConfig['server'],
474 $databaseConfig['username'],
475 $databaseConfig['password'],
477 ts("MySQL %1 Configuration", array(1 => $dbName)),
478 ts("Are the access credentials correct?"),
479 ts("That username/password doesn't work"),
483 @$this->requireMySQLVersion("5.1",
485 ts("MySQL %1 Configuration", array(1 => $dbName)),
486 ts("MySQL version at least %1", array(1 => '5.1')),
487 ts("MySQL version %1 or higher is required, you are running MySQL %2.", array(1 => '5.1', 2 => mysqli_get_server_info($this->conn
))),
488 ts("MySQL %1", array(1 => mysqli_get_server_info($this->conn
))),
491 $this->requireMySQLAutoIncrementIncrementOne($databaseConfig['server'],
492 $databaseConfig['username'],
493 $databaseConfig['password'],
495 ts("MySQL %1 Configuration", array(1 => $dbName)),
496 ts("Is auto_increment_increment set to 1"),
497 ts("An auto_increment_increment value greater than 1 is not currently supported. Please see issue CRM-7923 for further details and potential workaround."),
500 $testDetails = array(
501 ts("MySQL %1 Configuration", array(1 => $dbName)),
502 ts("Is the provided database name valid?"),
503 ts("The database name provided is not valid. Please use only 0-9, a-z, A-Z, _ and - as characters in the name."),
505 if (!CRM_Core_DAO
::requireSafeDBName($databaseConfig['database'])) {
506 $this->error($testDetails);
510 $this->testing($testDetails);
512 $this->requireMySQLThreadStack($databaseConfig['server'],
513 $databaseConfig['username'],
514 $databaseConfig['password'],
515 $databaseConfig['database'],
516 self
::MINIMUM_THREAD_STACK
,
518 ts("MySQL %1 Configuration", array(1 => $dbName)),
519 ts("Does MySQL thread_stack meet minimum (%1k)", array(1 => self
::MINIMUM_THREAD_STACK
)),
521 // "The MySQL thread_stack does not meet minimum " . CRM_Upgrade_Form::MINIMUM_THREAD_STACK . "k. Please update thread_stack in my.cnf.",
525 $onlyRequire = $dbName == 'Drupal' ||
$dbName == 'Backdrop';
526 $this->requireDatabaseOrCreatePermissions(
527 $databaseConfig['server'],
528 $databaseConfig['username'],
529 $databaseConfig['password'],
530 $databaseConfig['database'],
532 ts("MySQL %1 Configuration", array(1 => $dbName)),
533 ts("Can I access/create the database?"),
534 ts("I can't create new databases and the database '%1' doesn't exist.", array(1 => $databaseConfig['database'])),
538 if ($dbName != 'Drupal' && $dbName != 'Backdrop') {
539 $this->requireNoExistingData(
540 $databaseConfig['server'],
541 $databaseConfig['username'],
542 $databaseConfig['password'],
543 $databaseConfig['database'],
545 ts("MySQL %1 Configuration", array(1 => $dbName)),
546 ts("Does the database have data from a previous installation?"),
547 ts("CiviCRM data from previous installation exists in '%1'.", array(1 => $databaseConfig['database'])),
550 $this->requireMySQLInnoDB($databaseConfig['server'],
551 $databaseConfig['username'],
552 $databaseConfig['password'],
553 $databaseConfig['database'],
555 ts("MySQL %1 Configuration", array(1 => $dbName)),
556 ts("Can I access/create InnoDB tables in the database?"),
557 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."),
560 $this->requireMySQLTempTables($databaseConfig['server'],
561 $databaseConfig['username'],
562 $databaseConfig['password'],
563 $databaseConfig['database'],
565 ts("MySQL %1 Configuration", array(1 => $dbName)),
566 ts('Can I create temporary tables in the database?'),
567 ts('Unable to create temporary tables. This MySQL user is missing the CREATE TEMPORARY TABLES privilege.'),
570 $this->requireMySQLLockTables($databaseConfig['server'],
571 $databaseConfig['username'],
572 $databaseConfig['password'],
573 $databaseConfig['database'],
575 ts("MySQL %1 Configuration", array(1 => $dbName)),
576 ts('Can I create lock tables in the database?'),
577 ts('Unable to lock tables. This MySQL user is missing the LOCK TABLES privilege.'),
580 $this->requireMySQLTrigger($databaseConfig['server'],
581 $databaseConfig['username'],
582 $databaseConfig['password'],
583 $databaseConfig['database'],
585 ts("MySQL %1 Configuration", array(1 => $dbName)),
586 ts('Can I create triggers in the database?'),
587 ts('Unable to create triggers. This MySQL user is missing the CREATE TRIGGERS privilege.'),
590 $this->requireMySQLUtf8mb4($databaseConfig['server'],
591 $databaseConfig['username'],
592 $databaseConfig['password'],
593 $databaseConfig['database'],
595 ts("MySQL %1 Configuration", array(1 => $dbName)),
596 ts('Is the <code>utf8mb4</code> character set supported?'),
597 ts('This MySQL server does not support the <code>utf8mb4</code> character set.'),
605 * Connect via mysqli.
607 * This is exactly the same as mysqli_connect(), except that it accepts
608 * the port as part of the `$host`.
610 * @param string $host
611 * Ex: 'localhost', 'localhost:3307', '127.0.0.1:3307', '[::1]', '[::1]:3307'.
612 * @param string $username
613 * @param string $password
614 * @param string $database
617 protected function connect($host, $username, $password, $database = '') {
618 $hostParts = explode(':', $host);
619 if (count($hostParts) > 1 && strrpos($host, ']') !== strlen($host) - 1) {
620 $port = array_pop($hostParts);
621 $host = implode(':', $hostParts);
626 $conn = @mysqli_connect
($host, $username, $password, $database, $port);
631 * Check everything except the database.
633 public function check() {
634 global $crmPath, $installType;
636 $this->errors
= NULL;
638 $this->requirePHPVersion(array(
639 ts("PHP Configuration"),
640 ts("PHP7 installed"),
643 // Check that we can identify the root folder successfully
644 $this->requireFile($crmPath . CIVICRM_DIRECTORY_SEPARATOR
. 'README.md',
646 ts("File permissions"),
647 ts("Does the webserver know where files are stored?"),
648 ts("The webserver isn't letting me identify where files are stored."),
654 // CRM-6485: make sure the path does not contain PATH_SEPARATOR, as we don’t know how to escape it
655 $this->requireNoPathSeparator(
657 ts("File permissions"),
658 ts('Does the CiviCRM path contain PATH_SEPARATOR?'),
659 ts('The path %1 contains PATH_SEPARATOR (the %2 character).', array(1 => $this->getBaseDir(), 2 => PATH_SEPARATOR
)),
664 $requiredDirectories = array('CRM', 'packages', 'templates', 'js', 'api', 'i', 'sql');
665 foreach ($requiredDirectories as $dir) {
666 $this->requireFile($crmPath . CIVICRM_DIRECTORY_SEPARATOR
. $dir,
668 ts("File permissions"),
669 ts("Folder '%1' exists?", array(1 => $dir)),
670 ts("There is no '%1' folder.", array(1 => $dir)),
675 $configIDSiniDir = NULL;
677 $siteDir = getSiteDir($cmsPath, $_SERVER['SCRIPT_FILENAME']);
678 if ($installType == 'drupal') {
680 // make sure that we can write to sites/default and files/
681 $writableDirectories = array(
682 $cmsPath . CIVICRM_DIRECTORY_SEPARATOR
.
683 'sites' . CIVICRM_DIRECTORY_SEPARATOR
.
684 $siteDir . CIVICRM_DIRECTORY_SEPARATOR
.
686 $cmsPath . CIVICRM_DIRECTORY_SEPARATOR
.
687 'sites' . CIVICRM_DIRECTORY_SEPARATOR
.
691 elseif ($installType == 'backdrop') {
693 // make sure that we can write to sites/default and files/
694 $writableDirectories = array(
695 $cmsPath . CIVICRM_DIRECTORY_SEPARATOR
.
700 elseif ($installType == 'wordpress') {
701 // make sure that we can write to uploads/civicrm/
702 $upload_dir = wp_upload_dir();
703 $files_dirname = $upload_dir['basedir'] . DIRECTORY_SEPARATOR
. 'civicrm';
704 if (!file_exists($files_dirname)) {
705 wp_mkdir_p($files_dirname);
707 $writableDirectories = array($files_dirname);
710 foreach ($writableDirectories as $dir) {
711 $dirName = CIVICRM_WINDOWS ?
$dir : CIVICRM_DIRECTORY_SEPARATOR
. $dir;
712 $testDetails = array(
713 ts("File permissions"),
714 ts("Is the %1 folder writeable?", array(1 => $dir)),
717 $this->requireWriteable($dirName, $testDetails, TRUE);
720 // Check for rewriting
721 if (isset($_SERVER['SERVER_SOFTWARE'])) {
722 $webserver = strip_tags(trim($_SERVER['SERVER_SOFTWARE']));
724 elseif (isset($_SERVER['SERVER_SIGNATURE'])) {
725 $webserver = strip_tags(trim($_SERVER['SERVER_SIGNATURE']));
728 if ($webserver == '') {
729 $webserver = ts("I can't tell what webserver you are running");
732 // Check for $_SERVER configuration
733 $this->requireServerVariables(array('SCRIPT_NAME', 'HTTP_HOST', 'SCRIPT_FILENAME'), array(
734 ts("Webserver config"),
735 ts("Recognised webserver"),
736 ts("You seem to be using an unsupported webserver. The server variables SCRIPT_NAME, HTTP_HOST, SCRIPT_FILENAME need to be set."),
739 // Check for MySQL support
740 $this->requireFunction('mysqli_connect', array(
741 ts("PHP Configuration"),
743 ts("MySQL support not included in PHP."),
746 // Check for XML support
747 $this->requireFunction('simplexml_load_file', array(
748 ts("PHP Configuration"),
749 ts("SimpleXML support"),
750 ts("SimpleXML support not included in PHP."),
753 // Check for JSON support
754 $this->requireFunction('json_encode', array(
755 ts("PHP Configuration"),
757 ts("JSON support not included in PHP."),
760 // check for Multibyte support such as mb_substr. Required for proper handling of Multilingual setups.
761 $this->requireFunction('mb_substr', array(
762 ts("PHP Configuration"),
763 ts("Multibyte support"),
764 ts("Multibyte support not enabled in PHP."),
767 // Check for xcache_isset and emit warning if exists
768 $this->checkXCache(array(
769 ts("PHP Configuration"),
770 ts("XCache compatibility"),
771 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."),
774 // Check memory allocation
775 $this->requireMemory(32 * 1024 * 1024,
778 ts("PHP Configuration"),
779 ts("Memory allocated (PHP config option 'memory_limit')"),
780 ts("CiviCRM needs a minimum of %1 MB allocated to PHP, but recommends %2 MB.", array(1 => 32, 2 => 64)),
781 ini_get("memory_limit"),
785 return $this->errors
;
790 * @param $recommended
791 * @param $testDetails
793 public function requireMemory($min, $recommended, $testDetails) {
794 $this->testing($testDetails);
795 $mem = $this->getPHPMemory();
797 if ($mem < $min && $mem > 0) {
798 $testDetails[2] .= " " . ts("You only have %1 allocated", array(1 => ini_get("memory_limit")));
799 $this->error($testDetails);
801 elseif ($mem < $recommended && $mem > 0) {
802 $testDetails[2] .= " " . ts("You only have %1 allocated", array(1 => ini_get("memory_limit")));
803 $this->warning($testDetails);
806 $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));
807 $this->warning($testDetails);
814 public function getPHPMemory() {
815 $memString = ini_get("memory_limit");
817 switch (strtolower(substr($memString, -1))) {
819 return round(substr($memString, 0, -1) * 1024);
822 return round(substr($memString, 0, -1) * 1024 * 1024);
825 return round(substr($memString, 0, -1) * 1024 * 1024 * 1024);
828 return round($memString);
832 public function listErrors() {
834 echo "<p>" . ts("The following problems are preventing me from installing CiviCRM:") . "</p>";
835 foreach ($this->errors
as $error) {
836 echo "<li>" . htmlentities($error) . "</li>";
842 * @param null $section
844 public function showTable($section = NULL) {
846 $tests = $this->tests
[$section];
847 echo "<table class=\"testResults\" width=\"100%\">";
848 foreach ($tests as $test => $result) {
849 echo "<tr class=\"$result[0]\"><td>$test</td><td>" . nl2br(htmlentities($result[1])) . "</td></tr>";
854 foreach ($this->tests
as $section => $tests) {
855 echo "<h3>$section</h3>";
856 echo "<table class=\"testResults\" width=\"100%\">";
858 foreach ($tests as $test => $result) {
859 echo "<tr class=\"$result[0]\"><td>$test</td><td>" . nl2br(htmlentities($result[1])) . "</td></tr>";
867 * @param string $funcName
868 * @param $testDetails
872 public function requireFunction($funcName, $testDetails) {
873 $this->testing($testDetails);
875 if (!function_exists($funcName)) {
876 $this->error($testDetails);
885 * @param $testDetails
887 public function checkXCache($testDetails) {
888 if (function_exists('xcache_isset') &&
889 ini_get('xcache.size') > 0
891 $this->testing($testDetails);
892 $this->warning($testDetails);
897 * @param array $testDetails
900 public function requirePHPVersion($testDetails) {
902 $this->testing($testDetails);
904 $phpVersion = phpversion();
905 $aboveMinVersion = version_compare($phpVersion, CRM_Upgrade_Incremental_General
::MIN_INSTALL_PHP_VER
) >= 0;
907 if ($aboveMinVersion) {
908 if (version_compare($phpVersion, CRM_Upgrade_Incremental_General
::MIN_RECOMMENDED_PHP_VER
) < 0) {
909 $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(
911 2 => CRM_Upgrade_Incremental_General
::MIN_RECOMMENDED_PHP_VER
,
912 3 => preg_replace(';^(\d+\.\d+(?:\.[1-9]\d*)?).*$;', '\1', CRM_Upgrade_Incremental_General
::RECOMMENDED_PHP_VER
),
914 $this->warning($testDetails);
919 if (empty($testDetails[2])) {
920 $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));
923 $this->error($testDetails);
927 * @param string $filename
928 * @param $testDetails
929 * @param bool $absolute
931 public function requireFile($filename, $testDetails, $absolute = FALSE) {
932 $this->testing($testDetails);
934 $filename = $this->getBaseDir() . $filename;
936 if (!file_exists($filename)) {
937 $testDetails[2] .= " (" . ts("file '%1' not found", array(1 => $filename)) . ')';
938 $this->error($testDetails);
943 * @param $testDetails
945 public function requireNoPathSeparator($testDetails) {
946 $this->testing($testDetails);
947 if (substr_count($this->getBaseDir(), PATH_SEPARATOR
)) {
948 $this->error($testDetails);
953 * @param string $filename
954 * @param $testDetails
956 public function requireNoFile($filename, $testDetails) {
957 $this->testing($testDetails);
958 $filename = $this->getBaseDir() . $filename;
959 if (file_exists($filename)) {
960 $testDetails[2] .= " (" . ts("file '%1' found", array(1 => $filename)) . ")";
961 $this->error($testDetails);
966 * @param string $filename
967 * @param $testDetails
969 public function moveFileOutOfTheWay($filename, $testDetails) {
970 $this->testing($testDetails);
971 $filename = $this->getBaseDir() . $filename;
972 if (file_exists($filename)) {
973 if (file_exists("$filename.bak")) {
976 rename($filename, "$filename.bak");
981 * @param string $filename
982 * @param $testDetails
983 * @param bool $absolute
985 public function requireWriteable($filename, $testDetails, $absolute = FALSE) {
986 $this->testing($testDetails);
988 $filename = $this->getBaseDir() . $filename;
991 if (!is_writable($filename)) {
993 if (function_exists('posix_getpwuid')) {
994 $user = posix_getpwuid(posix_geteuid());
995 $name = '- ' . $user['name'] . ' -';
998 if (!isset($testDetails[2])) {
999 $testDetails[2] = NULL;
1001 $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";
1002 $this->error($testDetails);
1007 * @param string $moduleName
1008 * @param $testDetails
1010 public function requireApacheModule($moduleName, $testDetails) {
1011 $this->testing($testDetails);
1012 if (!in_array($moduleName, apache_get_modules())) {
1013 $this->error($testDetails);
1019 * @param string $username
1021 * @param $testDetails
1023 public function requireMysqlConnection($server, $username, $password, $testDetails) {
1024 $this->testing($testDetails);
1025 $this->conn
= $this->connect($server, $username, $password);
1031 $testDetails[2] .= ": " . mysqli_connect_error();
1032 $this->error($testDetails);
1038 * @param $testDetails
1040 public function requireMySQLServer($server, $testDetails) {
1041 $this->testing($testDetails);
1042 $conn = $this->connect($server, NULL, NULL);
1044 if ($conn ||
mysqli_connect_errno() < 2000) {
1048 $testDetails[2] .= ": " . mysqli_connect_error();
1049 $this->error($testDetails);
1055 * @param $testDetails
1057 public function requireMySQLVersion($version, $testDetails) {
1058 $this->testing($testDetails);
1060 if (!mysqli_get_server_info($this->conn
)) {
1061 $testDetails[2] = ts('Cannot determine the version of MySQL installed. Please ensure at least version %1 is installed.', array(1 => $version));
1062 $this->warning($testDetails);
1065 list($majorRequested, $minorRequested) = explode('.', $version);
1066 list($majorHas, $minorHas) = explode('.', mysqli_get_server_info($this->conn
));
1068 if (($majorHas > $majorRequested) ||
($majorHas == $majorRequested && $minorHas >= $minorRequested)) {
1072 $testDetails[2] .= "{$majorHas}.{$minorHas}.";
1073 $this->error($testDetails);
1080 * @param string $username
1083 * @param $testDetails
1085 public function requireMySQLInnoDB($server, $username, $password, $database, $testDetails) {
1086 $this->testing($testDetails);
1087 $conn = $this->connect($server, $username, $password);
1089 $testDetails[2] .= ' ' . ts("Could not determine if MySQL has InnoDB support. Assuming no.");
1090 $this->error($testDetails);
1094 $innodb_support = FALSE;
1095 $result = mysqli_query($conn, "SHOW ENGINES");
1096 while ($values = mysqli_fetch_array($result)) {
1097 if ($values['Engine'] == 'InnoDB') {
1098 if (strtolower($values['Support']) == 'yes' ||
1099 strtolower($values['Support']) == 'default'
1101 $innodb_support = TRUE;
1105 if ($innodb_support) {
1106 $testDetails[3] = ts('MySQL server does have InnoDB support');
1109 $testDetails[2] .= ' ' . ts('Could not determine if MySQL has InnoDB support. Assuming no');
1115 * @param string $username
1118 * @param $testDetails
1120 public function requireMySQLTempTables($server, $username, $password, $database, $testDetails) {
1121 $this->testing($testDetails);
1122 $conn = $this->connect($server, $username, $password);
1124 $testDetails[2] = ts('Could not login to the database.');
1125 $this->error($testDetails);
1129 if (!@mysqli_select_db
($conn, $database)) {
1130 $testDetails[2] = ts('Could not select the database.');
1131 $this->error($testDetails);
1135 $result = mysqli_query($conn, 'CREATE TEMPORARY TABLE civicrm_install_temp_table_test (test text)');
1137 $testDetails[2] = ts('Could not create a temp table.');
1138 $this->error($testDetails);
1140 $result = mysqli_query($conn, 'DROP TEMPORARY TABLE civicrm_install_temp_table_test');
1145 * @param string $username
1148 * @param $testDetails
1150 public function requireMySQLTrigger($server, $username, $password, $database, $testDetails) {
1151 $this->testing($testDetails);
1152 $conn = $this->connect($server, $username, $password);
1154 $testDetails[2] = ts('Could not login to the database.');
1155 $this->error($testDetails);
1159 if (!@mysqli_select_db
($conn, $database)) {
1160 $testDetails[2] = ts('Could not select the database.');
1161 $this->error($testDetails);
1165 $result = mysqli_query($conn, 'CREATE TABLE civicrm_install_temp_table_test (test text)');
1167 $testDetails[2] = ts('Could not create a table in the database.');
1168 $this->error($testDetails);
1171 $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');
1173 mysqli_query($conn, 'DROP TABLE civicrm_install_temp_table_test');
1174 $testDetails[2] = ts('Could not create a database trigger.');
1175 $this->error($testDetails);
1178 mysqli_query($conn, 'DROP TRIGGER civicrm_install_temp_table_test_trigger');
1179 mysqli_query($conn, 'DROP TABLE civicrm_install_temp_table_test');
1184 * @param string $username
1187 * @param $testDetails
1189 public function requireMySQLLockTables($server, $username, $password, $database, $testDetails) {
1190 $this->testing($testDetails);
1191 $conn = $this->connect($server, $username, $password);
1193 $testDetails[2] = ts('Could not connect to the database server.');
1194 $this->error($testDetails);
1198 if (!@mysqli_select_db
($conn, $database)) {
1199 $testDetails[2] = ts('Could not select the database.');
1200 $this->error($testDetails);
1204 $result = mysqli_query($conn, 'CREATE TEMPORARY TABLE civicrm_install_temp_table_test (test text)');
1206 $testDetails[2] = ts('Could not create a table in the database.');
1207 $this->error($testDetails);
1211 $result = mysqli_query($conn, 'LOCK TABLES civicrm_install_temp_table_test WRITE');
1213 $testDetails[2] = ts('Could not obtain a write lock for the database table.');
1214 $this->error($testDetails);
1215 $result = mysqli_query($conn, 'DROP TEMPORARY TABLE civicrm_install_temp_table_test');
1219 $result = mysqli_query($conn, 'UNLOCK TABLES');
1221 $testDetails[2] = ts('Could not release the lock for the database table.');
1222 $this->error($testDetails);
1223 $result = mysqli_query($conn, 'DROP TEMPORARY TABLE civicrm_install_temp_table_test');
1227 $result = mysqli_query($conn, 'DROP TEMPORARY TABLE civicrm_install_temp_table_test');
1232 * @param string $username
1234 * @param $testDetails
1236 public function requireMySQLAutoIncrementIncrementOne($server, $username, $password, $testDetails) {
1237 $this->testing($testDetails);
1238 $conn = $this->connect($server, $username, $password);
1240 $testDetails[2] = ts('Could not connect to the database server.');
1241 $this->error($testDetails);
1245 $result = mysqli_query($conn, "SHOW variables like 'auto_increment_increment'");
1247 $testDetails[2] = ts('Could not query database server variables.');
1248 $this->error($testDetails);
1252 $values = mysqli_fetch_row($result);
1253 if ($values[1] == 1) {
1254 $testDetails[3] = ts('MySQL server auto_increment_increment is 1');
1257 $this->error($testDetails);
1264 * @param string $username
1267 * @param $minValueKB
1268 * @param $testDetails
1270 public function requireMySQLThreadStack($server, $username, $password, $database, $minValueKB, $testDetails) {
1271 $this->testing($testDetails);
1272 $conn = $this->connect($server, $username, $password);
1274 $testDetails[2] = ts('Could not connect to the database server.');
1275 $this->error($testDetails);
1279 if (!@mysqli_select_db
($conn, $database)) {
1280 $testDetails[2] = ts('Could not select the database.');
1281 $this->error($testDetails);
1286 $result = mysqli_query($conn, "SHOW VARIABLES LIKE 'thread_stack'");
1288 $testDetails[2] = ts('Could not get information about the thread_stack of the database.');
1289 $this->error($testDetails);
1292 $values = mysqli_fetch_row($result);
1293 if ($values[1] < (1024 * $minValueKB)) {
1294 $testDetails[2] = ts('MySQL "thread_stack" is %1 kb', array(1 => ($values[1] / 1024)));
1295 $this->error($testDetails);
1305 * @param $testDetails
1307 public function requireNoExistingData(
1314 $this->testing($testDetails);
1315 $conn = $this->connect($server, $username, $password);
1317 @mysqli_select_db
($conn, $database);
1318 $contactRecords = mysqli_query($conn, "SELECT count(*) as contactscount FROM civicrm_contact");
1319 if ($contactRecords) {
1320 $contactRecords = mysqli_fetch_object($contactRecords);
1321 if ($contactRecords->contactscount
> 0) {
1322 $this->error($testDetails);
1327 $testDetails[3] = ts('CiviCRM data from previous installation does not exist in %1.', array(1 => $database));
1328 $this->testing($testDetails);
1333 * @param string $username
1336 * @param $testDetails
1337 * @param bool $onlyRequire
1339 public function requireDatabaseOrCreatePermissions(
1345 $onlyRequire = FALSE
1347 $this->testing($testDetails);
1348 $conn = $this->connect($server, $username, $password);
1351 if (@mysqli_select_db
($conn, $database)) {
1352 $okay = "Database '$database' exists";
1354 elseif ($onlyRequire) {
1355 $testDetails[2] = ts("The database: '%1' does not exist.", array(1 => $database));
1356 $this->error($testDetails);
1360 $query = sprintf("CREATE DATABASE %s", mysqli_real_escape_string($conn, $database));
1361 if (@mysqli_query
($conn, $query)) {
1362 $okay = ts("Able to create a new database.");
1365 $testDetails[2] .= " (" . ts("user '%1' doesn't have CREATE DATABASE permissions.", array(1 => $username)) . ")";
1366 $this->error($testDetails);
1372 $testDetails[3] = $okay;
1373 $this->testing($testDetails);
1379 * @param $errorMessage
1381 public function requireServerVariables($varNames, $errorMessage) {
1382 //$this->testing($testDetails);
1383 foreach ($varNames as $varName) {
1384 if (!$_SERVER[$varName]) {
1385 $missing[] = '$_SERVER[' . $varName . ']';
1388 if (!isset($missing)) {
1392 $testDetails[2] = " (" . ts('the following PHP variables are missing: %1', array(1 => implode(", ", $missing))) . ")";
1393 $this->error($testDetails);
1399 * @param string $username
1402 * @param $testDetails
1404 public function requireMysqlUtf8mb4($server, $username, $password, $database, $testDetails) {
1405 $this->testing($testDetails);
1406 $conn = $this->connect($server, $username, $password);
1408 $testDetails[2] = ts('Could not connect to the database server.');
1409 $this->error($testDetails);
1413 if (!@mysqli_select_db
($conn, $database)) {
1414 $testDetails[2] = ts('Could not select the database.');
1415 $this->error($testDetails);
1419 $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');
1421 $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');
1422 $this->warning($testDetails);
1425 $result = mysqli_query($conn, 'DROP TABLE civicrm_utf8mb4_test');
1427 // Ensure that the MySQL driver supports utf8mb4 encoding.
1428 $version = mysqli_get_client_info();
1429 if (strpos($version, 'mysqlnd') !== FALSE) {
1430 // The mysqlnd driver supports utf8mb4 starting at version 5.0.9.
1431 $version = preg_replace('/^\D+([\d.]+).*/', '$1', $version);
1432 if (version_compare($version, '5.0.9', '<')) {
1433 $testDetails[2] = 'It is recommended, though not yet required, to upgrade your PHP MySQL driver (mysqlnd) to >= 5.0.9 for utf8mb4 support.';
1434 $this->warning($testDetails);
1439 // The libmysqlclient driver supports utf8mb4 starting at version 5.5.3.
1440 if (version_compare($version, '5.5.3', '<')) {
1441 $testDetails[2] = 'It is recommended, though not yet required, to upgrade your PHP MySQL driver (libmysqlclient) to >= 5.5.3 for utf8mb4 support.';
1442 $this->warning($testDetails);
1449 * @param $testDetails
1453 public function isRunningApache($testDetails) {
1454 $this->testing($testDetails);
1455 if (function_exists('apache_get_modules') ||
stristr($_SERVER['SERVER_SIGNATURE'], 'Apache')) {
1459 $this->warning($testDetails);
1466 public function getBaseDir() {
1467 return dirname($_SERVER['SCRIPT_FILENAME']) . CIVICRM_DIRECTORY_SEPARATOR
;
1471 * @param $testDetails
1473 public function testing($testDetails) {
1474 if (!$testDetails) {
1478 $section = $testDetails[0];
1479 $test = $testDetails[1];
1481 $message = ts("OK");
1482 if (isset($testDetails[3])) {
1483 $message .= " ($testDetails[3])";
1486 $this->tests
[$section][$test] = array("good", $message);
1490 * @param $testDetails
1492 public function error($testDetails) {
1493 $section = $testDetails[0];
1494 $test = $testDetails[1];
1496 $this->tests
[$section][$test] = array("error", $testDetails[2]);
1497 $this->errors
[] = $testDetails;
1501 * @param $testDetails
1503 public function warning($testDetails) {
1504 $section = $testDetails[0];
1505 $test = $testDetails[1];
1507 $this->tests
[$section][$test] = array("warning", $testDetails[2]);
1508 $this->warnings
[] = $testDetails;
1514 public function hasErrors() {
1515 return !empty($this->errors
);
1521 public function hasWarnings() {
1522 return !empty($this->warnings
);
1530 class Installer
extends InstallRequirements
{
1538 public function createDatabaseIfNotExists($server, $username, $password, $database) {
1539 $conn = $this->connect($server, $username, $password);
1541 if (@mysqli_select_db
($conn, $database)) {
1542 // skip if database already present
1545 $query = sprintf("CREATE DATABASE %s", mysqli_real_escape_string($conn, $database));
1546 if (@mysqli_query
($conn, $query)) {
1549 $errorTitle = ts("Oops! Could not create database %1", array(1 => $database));
1550 $errorMsg = ts("We encountered an error when attempting to create the database. Please check your MySQL server permissions and the database name and try again.");
1551 errorDisplayPage($errorTitle, $errorMsg);
1560 public function install($config) {
1561 global $installDirPath;
1563 // create database if does not exists
1564 $this->createDatabaseIfNotExists($config['mysql']['server'],
1565 $config['mysql']['username'],
1566 $config['mysql']['password'],
1567 $config['mysql']['database']
1570 global $installDirPath;
1573 require_once $installDirPath . 'civicrm.php';
1574 civicrm_main($config);
1576 if (!$this->errors
) {
1577 global $installType, $installURLPath;
1579 $registerSiteURL = "https://civicrm.org/register-site";
1580 $commonOutputMessage
1581 = "<li>" . ts("Have you registered this site at CiviCRM.org? If not, please help strengthen the CiviCRM ecosystem by taking a few minutes to <a %1>fill out the site registration form</a>. The information collected will help us prioritize improvements, target our communications and build the community. If you have a technical role for this site, be sure to check Keep in Touch to receive technical updates (a low volume mailing list).", array(1 => "href='$registerSiteURL' target='_blank'")) . "</li>"
1582 . "<li>" . ts("We have integrated KCFinder with CKEditor and TinyMCE. This allows a user to upload images. All uploaded images are public.") . "</li>";
1587 $installType == 'drupal' &&
1588 version_compare(VERSION
, '7.0-rc1') >= 0
1594 $output .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
1595 $output .= '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">';
1596 $output .= '<head>';
1597 $output .= '<title>' . ts('CiviCRM Installed') . '</title>';
1598 $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
1599 $output .= '<link rel="stylesheet" type="text/css" href="template.css" />';
1600 $output .= '</head>';
1601 $output .= '<body>';
1602 $output .= '<div style="padding: 1em;"><p class="good">' . ts('CiviCRM has been successfully installed') . '</p>';
1605 $drupalURL = civicrm_cms_base();
1606 $drupalPermissionsURL = "{$drupalURL}index.php?q=admin/people/permissions";
1607 $drupalURL .= "index.php?q=civicrm/admin/configtask&reset=1";
1609 $output .= "<li>" . ts("Drupal user permissions have been automatically set - giving anonymous and authenticated users access to public CiviCRM forms and features. We recommend that you <a %1>review these permissions</a> to ensure that they are appropriate for your requirements (<a %2>learn more...</a>)", array(1 => "target='_blank' href='{$drupalPermissionsURL}'", 2 => "target='_blank' href='http://wiki.civicrm.org/confluence/display/CRMDOC/Default+Permissions+and+Roles'")) . "</li>";
1610 $output .= "<li>" . ts("Use the <a %1>Configuration Checklist</a> to review and configure settings for your new site", array(1 => "target='_blank' href='$drupalURL'")) . "</li>";
1611 $output .= $commonOutputMessage;
1613 // automatically enable CiviCRM module once it is installed successfully.
1614 // so we need to Bootstrap Drupal, so that we can call drupal hooks.
1615 global $cmsPath, $crmPath;
1617 // relative / abosolute paths are not working for drupal, hence using chdir()
1620 // Force the re-initialisation of the config singleton on the next call
1621 // since so far, we had used the Config object without loading the DB.
1622 $c = CRM_Core_Config
::singleton(FALSE);
1625 include_once "./includes/bootstrap.inc";
1626 include_once "./includes/unicode.inc";
1628 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL
);
1630 // prevent session information from being saved.
1631 drupal_save_session(FALSE);
1633 // Force the current user to anonymous.
1634 $original_user = $GLOBALS['user'];
1635 $GLOBALS['user'] = drupal_anonymous_user();
1637 // explicitly setting error reporting, since we cannot handle drupal related notices
1640 // rebuild modules, so that civicrm is added
1641 system_rebuild_module_data();
1643 // now enable civicrm module.
1644 module_enable(array('civicrm', 'civicrmtheme'));
1646 // SystemInstallEvent will be called from here with the first call of CRM_Core_Config,
1647 // which calls Core_BAO_ConfigSetting::applyLocale(), who will default to calling
1648 // Civi::settings()->get('lcMessages');
1649 // Therefore, we need to pass the seedLanguage before that.
1650 global $civicrm_setting;
1651 $civicrm_setting['domain']['lcMessages'] = $config['seedLanguage'];
1653 // clear block, page, theme, and hook caches
1654 drupal_flush_all_caches();
1656 //add basic drupal permissions
1657 civicrm_install_set_drupal_perms();
1659 // restore the user.
1660 $GLOBALS['user'] = $original_user;
1661 drupal_save_session(TRUE);
1664 $output .= '</div>';
1665 $output .= '</body>';
1666 $output .= '</html>';
1670 $installType == 'backdrop'
1676 $output .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
1677 $output .= '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">';
1678 $output .= '<head>';
1679 $output .= '<title>' . ts('CiviCRM Installed') . '</title>';
1680 $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
1681 $output .= '<link rel="stylesheet" type="text/css" href="template.css" />';
1682 $output .= '</head>';
1683 $output .= '<body>';
1684 $output .= '<div style="padding: 1em;"><p class="good">' . ts('CiviCRM has been successfully installed') . '</p>';
1687 $backdropURL = civicrm_cms_base();
1688 $backdropPermissionsURL = "{$backdropURL}index.php?q=admin/config/people/permissions";
1689 $backdropURL .= "index.php?q=civicrm/admin/configtask&reset=1";
1691 $output .= "<li>" . ts("Backdrop user permissions have been automatically set - giving anonymous and authenticated users access to public CiviCRM forms and features. We recommend that you <a %1>review these permissions</a> to ensure that they are appropriate for your requirements (<a %2>learn more...</a>)", array(1 => "target='_blank' href='{$backdropPermissionsURL}'", 2 => "target='_blank' href='http://wiki.civicrm.org/confluence/display/CRMDOC/Default+Permissions+and+Roles'")) . "</li>";
1692 $output .= "<li>" . ts("Use the <a %1>Configuration Checklist</a> to review and configure settings for your new site", array(1 => "target='_blank' href='$backdropURL'")) . "</li>";
1693 $output .= $commonOutputMessage;
1695 // automatically enable CiviCRM module once it is installed successfully.
1696 // so we need to Bootstrap Drupal, so that we can call drupal hooks.
1697 global $cmsPath, $crmPath;
1699 // relative / abosolute paths are not working for drupal, hence using chdir()
1702 // Force the re-initialisation of the config singleton on the next call
1703 // since so far, we had used the Config object without loading the DB.
1704 $c = CRM_Core_Config
::singleton(FALSE);
1707 include_once "./core/includes/bootstrap.inc";
1708 include_once "./core/includes/unicode.inc";
1709 include_once "./core/includes/config.inc";
1711 backdrop_bootstrap(BACKDROP_BOOTSTRAP_FULL
);
1713 // prevent session information from being saved.
1714 backdrop_save_session(FALSE);
1716 // Force the current user to anonymous.
1717 $original_user = $GLOBALS['user'];
1718 $GLOBALS['user'] = backdrop_anonymous_user();
1720 // explicitly setting error reporting, since we cannot handle drupal related notices
1723 // rebuild modules, so that civicrm is added
1724 system_rebuild_module_data();
1726 // now enable civicrm module.
1727 module_enable(array('civicrm', 'civicrmtheme'));
1729 // clear block, page, theme, and hook caches
1730 backdrop_flush_all_caches();
1732 //add basic backdrop permissions
1733 civicrm_install_set_backdrop_perms();
1735 // restore the user.
1736 $GLOBALS['user'] = $original_user;
1737 backdrop_save_session(TRUE);
1739 //change the default language to one chosen
1740 if (isset($config['seedLanguage']) && $config['seedLanguage'] != 'en_US') {
1741 civicrm_api3('Setting', 'create', array(
1742 'domain_id' => 'current_domain',
1743 'lcMessages' => $config['seedLanguage'],
1748 $output .= '</div>';
1749 $output .= '</body>';
1750 $output .= '</html>';
1753 elseif ($installType == 'drupal' && version_compare(VERSION
, '6.0') >= 0) {
1757 $output .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
1758 $output .= '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">';
1759 $output .= '<head>';
1760 $output .= '<title>' . ts('CiviCRM Installed') . '</title>';
1761 $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
1762 $output .= '<link rel="stylesheet" type="text/css" href="template.css" />';
1763 $output .= '</head>';
1764 $output .= '<body>';
1765 $output .= '<div style="padding: 1em;"><p class="good">' . ts("CiviCRM has been successfully installed") . '</p>';
1768 $drupalURL = civicrm_cms_base();
1769 $drupalPermissionsURL = "{$drupalURL}index.php?q=admin/user/permissions";
1770 $drupalURL .= "index.php?q=civicrm/admin/configtask&reset=1";
1772 $output .= "<li>" . ts("Drupal user permissions have been automatically set - giving anonymous and authenticated users access to public CiviCRM forms and features. We recommend that you <a %1>review these permissions</a> to ensure that they are appropriate for your requirements (<a %2>learn more...</a>)", array(1 => "target='_blank' href='{$drupalPermissionsURL}'", 2 => "target='_blank' href='http://wiki.civicrm.org/confluence/display/CRMDOC/Default+Permissions+and+Roles'")) . "</li>";
1773 $output .= "<li>" . ts("Use the <a %1>Configuration Checklist</a> to review and configure settings for your new site", array(1 => "target='_blank' href='$drupalURL'")) . "</li>";
1774 $output .= $commonOutputMessage;
1776 // explicitly setting error reporting, since we cannot handle drupal related notices
1779 // automatically enable CiviCRM module once it is installed successfully.
1780 // so we need to Bootstrap Drupal, so that we can call drupal hooks.
1781 global $cmsPath, $crmPath;
1783 // relative / abosolute paths are not working for drupal, hence using chdir()
1786 // Force the re-initialisation of the config singleton on the next call
1787 // since so far, we had used the Config object without loading the DB.
1788 $c = CRM_Core_Config
::singleton(FALSE);
1791 include_once "./includes/bootstrap.inc";
1792 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL
);
1794 // rebuild modules, so that civicrm is added
1795 module_rebuild_cache();
1797 // now enable civicrm module.
1798 module_enable(array('civicrm'));
1800 // clear block, page, theme, and hook caches
1801 drupal_flush_all_caches();
1803 //add basic drupal permissions
1804 db_query('UPDATE {permission} SET perm = CONCAT( perm, \', access CiviMail subscribe/unsubscribe pages, access all custom data, access uploaded files, make online contributions, profile create, profile edit, profile view, register for events, view event info\') WHERE rid IN (1, 2)');
1808 elseif ($installType == 'wordpress') {
1809 echo '<h1>' . ts('CiviCRM Installed') . '</h1>';
1810 echo '<div style="padding: 1em;"><p style="background-color: #0C0; border: 1px #070 solid; color: white;">' . ts("CiviCRM has been successfully installed") . '</p>';
1813 $cmsURL = civicrm_cms_base();
1814 $cmsURL .= "wp-admin/admin.php?page=CiviCRM&q=civicrm/admin/configtask&reset=1";
1815 $wpPermissionsURL = "wp-admin/admin.php?page=CiviCRM&q=civicrm/admin/access/wp-permissions&reset=1";
1817 $output .= "<li>" . ts("WordPress user permissions have been automatically set - giving Anonymous and Subscribers access to public CiviCRM forms and features. We recommend that you <a %1>review these permissions</a> to ensure that they are appropriate for your requirements (<a %2>learn more...</a>)", array(1 => "target='_blank' href='{$wpPermissionsURL}'", 2 => "target='_blank' href='http://wiki.civicrm.org/confluence/display/CRMDOC/Default+Permissions+and+Roles'")) . "</li>";
1818 $output .= "<li>" . ts("Use the <a %1>Configuration Checklist</a> to review and configure settings for your new site", array(1 => "target='_blank' href='$cmsURL'")) . "</li>";
1819 $output .= $commonOutputMessage;
1822 $output .= '</div>';
1825 $c = CRM_Core_Config
::singleton(FALSE);
1827 $wpInstallRedirect = admin_url('admin.php?page=CiviCRM&q=civicrm&reset=1');
1829 window.location = '$wpInstallRedirect';
1834 return $this->errors
;
1839 function civicrm_install_set_drupal_perms() {
1840 if (!function_exists('db_select')) {
1841 db_query('UPDATE {permission} SET perm = CONCAT( perm, \', access CiviMail subscribe/unsubscribe pages, access all custom data, access uploaded files, make online contributions, profile listings and forms, register for events, view event info, view event participants\') WHERE rid IN (1, 2)');
1845 'access all custom data',
1846 'access uploaded files',
1847 'make online contributions',
1851 'register for events',
1853 'view event participants',
1854 'access CiviMail subscribe/unsubscribe pages',
1857 // Adding a permission that has not yet been assigned to a module by
1858 // a hook_permission implementation results in a database error.
1860 $allPerms = array_keys(module_invoke_all('permission'));
1861 foreach (array_diff($perms, $allPerms) as $perm) {
1863 'Cannot grant the %perm permission because it does not yet exist.',
1864 array('%perm' => $perm),
1868 $perms = array_intersect($perms, $allPerms);
1869 user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID
, $perms);
1870 user_role_grant_permissions(DRUPAL_ANONYMOUS_RID
, $perms);
1874 function civicrm_install_set_backdrop_perms() {
1876 'access all custom data',
1877 'access uploaded files',
1878 'make online contributions',
1882 'register for events',
1884 'view event participants',
1885 'access CiviMail subscribe/unsubscribe pages',
1888 // Adding a permission that has not yet been assigned to a module by
1889 // a hook_permission implementation results in a database error.
1891 $allPerms = array_keys(module_invoke_all('permission'));
1892 foreach (array_diff($perms, $allPerms) as $perm) {
1894 'Cannot grant the %perm permission because it does not yet exist.',
1895 array('%perm' => $perm),
1899 $perms = array_intersect($perms, $allPerms);
1900 user_role_grant_permissions(BACKDROP_AUTHENTICATED_ROLE
, $perms);
1901 user_role_grant_permissions(BACKDROP_ANONYMOUS_ROLE
, $perms);
1910 function getSiteDir($cmsPath, $str) {
1911 static $siteDir = '';
1917 $sites = CIVICRM_DIRECTORY_SEPARATOR
. 'sites' . CIVICRM_DIRECTORY_SEPARATOR
;
1918 $modules = CIVICRM_DIRECTORY_SEPARATOR
. 'modules' . CIVICRM_DIRECTORY_SEPARATOR
;
1919 preg_match("/" . preg_quote($sites, CIVICRM_DIRECTORY_SEPARATOR
) .
1920 "([\-a-zA-Z0-9_.]+)" .
1921 preg_quote($modules, CIVICRM_DIRECTORY_SEPARATOR
) . "/",
1922 $_SERVER['SCRIPT_FILENAME'], $matches
1924 $siteDir = $matches[1] ??
'default';
1926 if (strtolower($siteDir) == 'all') {
1927 // For this case - use drupal's way of finding out multi-site directory
1928 $uri = explode(CIVICRM_DIRECTORY_SEPARATOR
, $_SERVER['SCRIPT_FILENAME']);
1929 $server = explode('.', implode('.', array_reverse(explode(':', rtrim($_SERVER['HTTP_HOST'], '.')))));
1930 for ($i = count($uri) - 1; $i > 0; $i--) {
1931 for ($j = count($server); $j > 0; $j--) {
1932 $dir = implode('.', array_slice($server, -$j)) . implode('.', array_slice($uri, 0, $i));
1933 if (file_exists($cmsPath . CIVICRM_DIRECTORY_SEPARATOR
.
1934 'sites' . CIVICRM_DIRECTORY_SEPARATOR
. $dir
1941 $siteDir = 'default';
1948 * @param $errorTitle
1952 function errorDisplayPage($errorTitle, $errorMsg, $showRefer = TRUE) {
1954 // Add a link to the documentation
1956 if (is_callable(array('CRM_Utils_System', 'docURL2'))) {
1957 $docLink = CRM_Utils_System
::docURL2('Installation and Upgrades', FALSE, 'Installation Guide', NULL, NULL, "wiki");
1963 if (function_exists('ts')) {
1964 $errorMsg .= '<p>' . ts("Refer to the online documentation for more information: ") . $docLink . '</p>';
1967 $errorMsg .= '<p>' . 'Refer to the online documentation for more information: ' . $docLink . '</p>';
1971 include 'error.html';