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 = (isset($installTypeToUF[$installType]) ?
$installTypeToUF[$installType] : 'Drupal');
133 define('CIVICRM_UF', $uf);
135 // Set the Locale (required by CRM_Core_Config)
139 $seedLanguage = 'en_US';
141 // CRM-16801 This validates that seedLanguage is valid by looking in $langs.
142 // NB: the variable is initial a $_REQUEST for the initial page reload,
143 // then becomes a $_POST when the installation form is submitted.
144 if (isset($_REQUEST['seedLanguage']) and isset($langs[$_REQUEST['seedLanguage']])) {
145 $seedLanguage = $_REQUEST['seedLanguage'];
146 $tsLocale = $_REQUEST['seedLanguage'];
149 $config = CRM_Core_Config
::singleton(FALSE);
150 $GLOBALS['civicrm_default_error_scope'] = NULL;
152 // The translation files are in the parent directory (l10n)
153 $i18n = CRM_Core_I18n
::singleton();
155 // Support for Arabic, Hebrew, Farsi, etc.
156 // Used in the template.html
157 $short_lang_code = CRM_Core_I18n_PseudoConstant
::shortForLong($tsLocale);
158 $text_direction = (CRM_Core_I18n
::isLanguageRTL($tsLocale) ?
'rtl' : 'ltr');
161 if ($installType == 'drupal') {
162 //CRM-6840 -don't force to install in sites/all/modules/
163 $object = new CRM_Utils_System_Drupal();
164 $cmsPath = $object->cmsRootPath();
166 $siteDir = getSiteDir($cmsPath, $_SERVER['SCRIPT_FILENAME']);
167 $alreadyInstalled = file_exists($cmsPath . CIVICRM_DIRECTORY_SEPARATOR
.
168 'sites' . CIVICRM_DIRECTORY_SEPARATOR
.
169 $siteDir . CIVICRM_DIRECTORY_SEPARATOR
.
170 'civicrm.settings.php'
173 elseif ($installType == 'backdrop') {
174 $object = new CRM_Utils_System_Backdrop();
175 $cmsPath = $object->cmsRootPath();
176 $siteDir = getSiteDir($cmsPath, $_SERVER['SCRIPT_FILENAME']);
177 $alreadyInstalled = file_exists($cmsPath . CIVICRM_DIRECTORY_SEPARATOR
. 'civicrm.settings.php');
179 elseif ($installType == 'wordpress') {
180 $cmsPath = WP_PLUGIN_DIR
. DIRECTORY_SEPARATOR
. 'civicrm';
181 $upload_dir = wp_upload_dir();
182 $files_dirname = $upload_dir['basedir'] . DIRECTORY_SEPARATOR
. 'civicrm';
183 $wp_civi_settings = $upload_dir['basedir'] . DIRECTORY_SEPARATOR
. 'civicrm' . DIRECTORY_SEPARATOR
. 'civicrm.settings.php';
184 $wp_civi_settings_deprectated = CIVICRM_PLUGIN_DIR
. 'civicrm.settings.php';
185 if (file_exists($wp_civi_settings_deprectated)) {
186 $alreadyInstalled = $wp_civi_settings_deprectated;
188 elseif (file_exists($wp_civi_settings)) {
189 $alreadyInstalled = $wp_civi_settings;
193 if ($installType == 'drupal') {
194 // Lets check only /modules/.
195 $pattern = '/' . preg_quote(CIVICRM_DIRECTORY_SEPARATOR
. 'modules', CIVICRM_DIRECTORY_SEPARATOR
) . '/';
197 if (!preg_match($pattern, str_replace("\\", "/", $_SERVER['SCRIPT_FILENAME']))) {
198 $directory = implode(CIVICRM_DIRECTORY_SEPARATOR
, array('sites', 'all', 'modules'));
199 $errorTitle = ts("Oops! Please correct your install location");
200 $errorMsg = ts("Please untar (uncompress) your downloaded copy of CiviCRM in the <strong>%1</strong> directory below your Drupal root directory.", array(1 => $directory));
201 errorDisplayPage($errorTitle, $errorMsg);
205 if ($installType == 'backdrop') {
206 // Lets check only /modules/.
207 $pattern = '/' . preg_quote(CIVICRM_DIRECTORY_SEPARATOR
. 'modules', CIVICRM_DIRECTORY_SEPARATOR
) . '/';
209 if (!preg_match($pattern, str_replace("\\", "/", $_SERVER['SCRIPT_FILENAME']))) {
210 $directory = 'modules';
211 $errorTitle = ts("Oops! Please correct your install location");
212 $errorMsg = ts("Please untar (uncompress) your downloaded copy of CiviCRM in the <strong>%1</strong> directory below your Drupal root directory.", array(1 => $directory));
213 errorDisplayPage($errorTitle, $errorMsg);
217 // Exit with error if CiviCRM has already been installed.
218 if ($alreadyInstalled) {
219 $errorTitle = ts("Oops! CiviCRM is already installed");
220 $settings_directory = $cmsPath;
222 if ($installType == 'drupal') {
223 $settings_directory = implode(CIVICRM_DIRECTORY_SEPARATOR
, array(
224 ts('[your Drupal root directory]'),
229 if ($installType == 'backdrop') {
230 $settings_directory = implode(CIVICRM_DIRECTORY_SEPARATOR
, array(
231 ts('[your Backdrop root directory]'),
236 $docLink = CRM_Utils_System
::docURL2('Installation and Upgrades', FALSE, ts('Installation Guide'), NULL, NULL, "wiki");
237 $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));
238 errorDisplayPage($errorTitle, $errorMsg, FALSE);
241 $versionFile = $crmPath . CIVICRM_DIRECTORY_SEPARATOR
. 'civicrm-version.php';
242 if (file_exists($versionFile)) {
243 require_once $versionFile;
244 $civicrm_version = civicrmVersion();
247 $civicrm_version = 'unknown';
250 if ($installType == 'drupal') {
251 // Ensure that they have downloaded the correct version of CiviCRM
252 if ($civicrm_version['cms'] != 'Drupal' && $civicrm_version['cms'] != 'Drupal6') {
253 $errorTitle = ts("Oops! Incorrect CiviCRM version");
254 $errorMsg = ts("This installer can only be used for the Drupal version of CiviCRM.");
255 errorDisplayPage($errorTitle, $errorMsg);
258 define('DRUPAL_ROOT', $cmsPath);
259 $drupalVersionFiles = array(
261 implode(CIVICRM_DIRECTORY_SEPARATOR
, array($cmsPath, 'modules', 'system', 'system.module')),
263 implode(CIVICRM_DIRECTORY_SEPARATOR
, array($cmsPath, 'includes', 'bootstrap.inc')),
265 foreach ($drupalVersionFiles as $drupalVersionFile) {
266 if (file_exists($drupalVersionFile)) {
267 require_once $drupalVersionFile;
271 // Bootstrap Drupal to get settings and user
272 $base_root = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ?
'https' : 'http';
273 $base_root .= '://' . $_SERVER['HTTP_HOST'];
274 $base_url = $base_root;
275 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL
);
277 // Check that user is logged in and has administrative permissions
278 // This is necessary because the script exposes the database settings in the form and these could be viewed by unauthorised users
279 if ((!function_exists('user_access')) ||
(!user_access('administer site configuration'))) {
280 $errorTitle = ts("You don't have permission to access this page");
281 $errorMsg = ts("The installer can only be run by a user with the permission to administer site configuration.");
282 errorDisplayPage($errorTitle, $errorMsg);
286 if (!defined('VERSION') or version_compare(VERSION
, '6.0') < 0) {
287 $errorTitle = ts("Oops! Incorrect Drupal version");
288 $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)));
289 errorDisplayPage($errorTitle, $errorMsg);
292 elseif ($installType == 'backdrop') {
293 // Ensure that they have downloaded the correct version of CiviCRM
294 if ($civicrm_version['cms'] != 'Backdrop') {
295 $errorTitle = ts("Oops! Incorrect CiviCRM version");
296 $errorMsg = ts("This installer can only be used for the Backdrop version of CiviCRM.");
297 errorDisplayPage($errorTitle, $errorMsg);
300 define('BACKDROP_ROOT', $cmsPath);
302 $backdropVersionFiles = array(
304 implode(CIVICRM_DIRECTORY_SEPARATOR
, array($cmsPath, 'core', 'includes', 'bootstrap.inc')),
306 foreach ($backdropVersionFiles as $backdropVersionFile) {
307 if (file_exists($backdropVersionFile)) {
308 require_once $backdropVersionFile;
311 if (!defined('BACKDROP_VERSION') or version_compare(BACKDROP_VERSION
, '1.0') < 0) {
312 $errorTitle = ts("Oops! Incorrect Backdrop version");
313 $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)));
314 errorDisplayPage($errorTitle, $errorMsg);
317 elseif ($installType == 'wordpress') {
319 $civicrm_version['cms'] = 'WordPress';
321 // Ensure that they have downloaded the correct version of CiviCRM
322 if ($civicrm_version['cms'] != 'WordPress') {
323 $errorTitle = ts("Oops! Incorrect CiviCRM version");
324 $errorMsg = ts("This installer can only be used for the WordPress version of CiviCRM.");
325 errorDisplayPage($errorTitle, $errorMsg);
329 // Load CiviCRM database config
330 if (isset($_POST['mysql'])) {
331 $databaseConfig = $_POST['mysql'];
334 if ($installType == 'wordpress') {
335 // Load WP database config
336 if (isset($_POST['mysql'])) {
337 $databaseConfig = $_POST['mysql'];
340 $databaseConfig = array(
342 "username" => DB_USER
,
343 "password" => DB_PASSWORD
,
344 "database" => DB_NAME
,
349 if ($installType == 'drupal') {
350 // Load drupal database config
351 if (isset($_POST['drupal'])) {
352 $drupalConfig = $_POST['drupal'];
355 $dbServer = $databases['default']['default']['host'];
356 if (!empty($databases['default']['default']['port'])) {
357 $dbServer .= ':' . $databases['default']['default']['port'];
359 $drupalConfig = array(
360 "server" => $dbServer,
361 "username" => $databases['default']['default']['username'],
362 "password" => $databases['default']['default']['password'],
363 "database" => $databases['default']['default']['database'],
368 if ($installType == 'backdrop') {
369 // Load backdrop database config
370 if (isset($_POST['backdrop'])) {
371 $backdropConfig = $_POST['backdrop'];
374 $backdropConfig = array(
375 "server" => "localhost",
376 "username" => "backdrop",
378 "database" => "backdrop",
383 // By default set CiviCRM database to be same as CMS database
384 if (!isset($databaseConfig)) {
385 if (($installType == 'drupal') && (isset($drupalConfig))) {
386 $databaseConfig = $drupalConfig;
388 if (($installType == 'backdrop') && (isset($backdropConfig))) {
389 $databaseConfig = $backdropConfig;
393 // Check requirements
394 $req = new InstallRequirements();
397 if ($req->hasErrors()) {
398 $hasErrorOtherThanDatabase = TRUE;
401 if ($databaseConfig) {
402 $dbReq = new InstallRequirements();
403 $dbReq->checkdatabase($databaseConfig, 'CiviCRM');
404 if ($installType == 'drupal') {
405 $dbReq->checkdatabase($drupalConfig, 'Drupal');
407 if ($installType == 'backdrop') {
408 $dbReq->checkdatabase($backdropConfig, 'Backdrop');
413 if (isset($_POST['go']) && !$req->hasErrors() && !$dbReq->hasErrors()) {
414 // Confirm before reinstalling
415 if (!isset($_POST['force_reinstall']) && $alreadyInstalled) {
416 include $installDirPath . 'template.html';
419 $inst = new Installer();
420 $inst->install($_POST);
423 // Show the config form
426 include $installDirPath . 'template.html';
430 * This class checks requirements
431 * Each of the requireXXX functions takes an argument which gives a user description of the test. It's an array
433 * $description[0] - The test category
434 * $description[1] - The test title
435 * $description[2] - The test error to show, if it goes wrong
437 class InstallRequirements
{
443 // @see CRM_Upgrade_Form::MINIMUM_THREAD_STACK
444 const MINIMUM_THREAD_STACK
= 192;
447 * Just check that the database configuration is okay.
448 * @param $databaseConfig
451 public function checkdatabase($databaseConfig, $dbName) {
452 if ($this->requireFunction('mysqli_connect',
454 ts("PHP Configuration"),
456 ts("MySQL support not included in PHP."),
460 $this->requireMySQLServer($databaseConfig['server'],
462 ts("MySQL %1 Configuration", array(1 => $dbName)),
463 ts("Does the server exist?"),
464 ts("Can't find the a MySQL server on '%1'.", array(1 => $databaseConfig['server'])),
465 $databaseConfig['server'],
468 if ($this->requireMysqlConnection($databaseConfig['server'],
469 $databaseConfig['username'],
470 $databaseConfig['password'],
472 ts("MySQL %1 Configuration", array(1 => $dbName)),
473 ts("Are the access credentials correct?"),
474 ts("That username/password doesn't work"),
478 @$this->requireMySQLVersion("5.1",
480 ts("MySQL %1 Configuration", array(1 => $dbName)),
481 ts("MySQL version at least %1", array(1 => '5.1')),
482 ts("MySQL version %1 or higher is required, you are running MySQL %2.", array(1 => '5.1', 2 => mysqli_get_server_info($this->conn
))),
483 ts("MySQL %1", array(1 => mysqli_get_server_info($this->conn
))),
486 $this->requireMySQLAutoIncrementIncrementOne($databaseConfig['server'],
487 $databaseConfig['username'],
488 $databaseConfig['password'],
490 ts("MySQL %1 Configuration", array(1 => $dbName)),
491 ts("Is auto_increment_increment set to 1"),
492 ts("An auto_increment_increment value greater than 1 is not currently supported. Please see issue CRM-7923 for further details and potential workaround."),
495 $testDetails = array(
496 ts("MySQL %1 Configuration", array(1 => $dbName)),
497 ts("Is the provided database name valid?"),
498 ts("The database name provided is not valid. Please use only 0-9, a-z, A-Z, _ and - as characters in the name."),
500 if (!CRM_Core_DAO
::requireSafeDBName($databaseConfig['database'])) {
501 $this->error($testDetails);
505 $this->testing($testDetails);
507 $this->requireMySQLThreadStack($databaseConfig['server'],
508 $databaseConfig['username'],
509 $databaseConfig['password'],
510 $databaseConfig['database'],
511 self
::MINIMUM_THREAD_STACK
,
513 ts("MySQL %1 Configuration", array(1 => $dbName)),
514 ts("Does MySQL thread_stack meet minimum (%1k)", array(1 => self
::MINIMUM_THREAD_STACK
)),
516 // "The MySQL thread_stack does not meet minimum " . CRM_Upgrade_Form::MINIMUM_THREAD_STACK . "k. Please update thread_stack in my.cnf.",
520 $onlyRequire = ($dbName == 'Drupal' ||
$dbName == 'Backdrop') ?
TRUE : FALSE;
521 $this->requireDatabaseOrCreatePermissions(
522 $databaseConfig['server'],
523 $databaseConfig['username'],
524 $databaseConfig['password'],
525 $databaseConfig['database'],
527 ts("MySQL %1 Configuration", array(1 => $dbName)),
528 ts("Can I access/create the database?"),
529 ts("I can't create new databases and the database '%1' doesn't exist.", array(1 => $databaseConfig['database'])),
533 if ($dbName != 'Drupal' && $dbName != 'Backdrop') {
534 $this->requireNoExistingData(
535 $databaseConfig['server'],
536 $databaseConfig['username'],
537 $databaseConfig['password'],
538 $databaseConfig['database'],
540 ts("MySQL %1 Configuration", array(1 => $dbName)),
541 ts("Does the database have data from a previous installation?"),
542 ts("CiviCRM data from previous installation exists in '%1'.", array(1 => $databaseConfig['database'])),
545 $this->requireMySQLInnoDB($databaseConfig['server'],
546 $databaseConfig['username'],
547 $databaseConfig['password'],
548 $databaseConfig['database'],
550 ts("MySQL %1 Configuration", array(1 => $dbName)),
551 ts("Can I access/create InnoDB tables in the database?"),
552 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."),
555 $this->requireMySQLTempTables($databaseConfig['server'],
556 $databaseConfig['username'],
557 $databaseConfig['password'],
558 $databaseConfig['database'],
560 ts("MySQL %1 Configuration", array(1 => $dbName)),
561 ts('Can I create temporary tables in the database?'),
562 ts('Unable to create temporary tables. This MySQL user is missing the CREATE TEMPORARY TABLES privilege.'),
565 $this->requireMySQLLockTables($databaseConfig['server'],
566 $databaseConfig['username'],
567 $databaseConfig['password'],
568 $databaseConfig['database'],
570 ts("MySQL %1 Configuration", array(1 => $dbName)),
571 ts('Can I create lock tables in the database?'),
572 ts('Unable to lock tables. This MySQL user is missing the LOCK TABLES privilege.'),
575 $this->requireMySQLTrigger($databaseConfig['server'],
576 $databaseConfig['username'],
577 $databaseConfig['password'],
578 $databaseConfig['database'],
580 ts("MySQL %1 Configuration", array(1 => $dbName)),
581 ts('Can I create triggers in the database?'),
582 ts('Unable to create triggers. This MySQL user is missing the CREATE TRIGGERS privilege.'),
585 $this->requireMySQLUtf8mb4($databaseConfig['server'],
586 $databaseConfig['username'],
587 $databaseConfig['password'],
588 $databaseConfig['database'],
590 ts("MySQL %1 Configuration", array(1 => $dbName)),
591 ts('Is the <code>utf8mb4</code> character set supported?'),
592 ts('This MySQL server does not support the <code>utf8mb4</code> character set.'),
600 * Connect via mysqli.
602 * This is exactly the same as mysqli_connect(), except that it accepts
603 * the port as part of the `$host`.
605 * @param string $host
606 * Ex: 'localhost', 'localhost:3307', '127.0.0.1:3307', '[::1]', '[::1]:3307'.
607 * @param string $username
608 * @param string $password
609 * @param string $database
612 protected function connect($host, $username, $password, $database = '') {
613 $hostParts = explode(':', $host);
614 if (count($hostParts) > 1 && strrpos($host, ']') !== strlen($host) - 1) {
615 $port = array_pop($hostParts);
616 $host = implode(':', $hostParts);
621 $conn = @mysqli_connect
($host, $username, $password, $database, $port);
626 * Check everything except the database.
628 public function check() {
629 global $crmPath, $installType;
631 $this->errors
= NULL;
633 $this->requirePHPVersion(array(
634 ts("PHP Configuration"),
635 ts("PHP7 installed"),
638 // Check that we can identify the root folder successfully
639 $this->requireFile($crmPath . CIVICRM_DIRECTORY_SEPARATOR
. 'README.md',
641 ts("File permissions"),
642 ts("Does the webserver know where files are stored?"),
643 ts("The webserver isn't letting me identify where files are stored."),
649 // CRM-6485: make sure the path does not contain PATH_SEPARATOR, as we don’t know how to escape it
650 $this->requireNoPathSeparator(
652 ts("File permissions"),
653 ts('Does the CiviCRM path contain PATH_SEPARATOR?'),
654 ts('The path %1 contains PATH_SEPARATOR (the %2 character).', array(1 => $this->getBaseDir(), 2 => PATH_SEPARATOR
)),
659 $requiredDirectories = array('CRM', 'packages', 'templates', 'js', 'api', 'i', 'sql');
660 foreach ($requiredDirectories as $dir) {
661 $this->requireFile($crmPath . CIVICRM_DIRECTORY_SEPARATOR
. $dir,
663 ts("File permissions"),
664 ts("Folder '%1' exists?", array(1 => $dir)),
665 ts("There is no '%1' folder.", array(1 => $dir)),
670 $configIDSiniDir = NULL;
672 $siteDir = getSiteDir($cmsPath, $_SERVER['SCRIPT_FILENAME']);
673 if ($installType == 'drupal') {
675 // make sure that we can write to sites/default and files/
676 $writableDirectories = array(
677 $cmsPath . CIVICRM_DIRECTORY_SEPARATOR
.
678 'sites' . CIVICRM_DIRECTORY_SEPARATOR
.
679 $siteDir . CIVICRM_DIRECTORY_SEPARATOR
.
681 $cmsPath . CIVICRM_DIRECTORY_SEPARATOR
.
682 'sites' . CIVICRM_DIRECTORY_SEPARATOR
.
686 elseif ($installType == 'backdrop') {
688 // make sure that we can write to sites/default and files/
689 $writableDirectories = array(
690 $cmsPath . CIVICRM_DIRECTORY_SEPARATOR
.
695 elseif ($installType == 'wordpress') {
696 // make sure that we can write to uploads/civicrm/
697 $upload_dir = wp_upload_dir();
698 $files_dirname = $upload_dir['basedir'] . DIRECTORY_SEPARATOR
. 'civicrm';
699 if (!file_exists($files_dirname)) {
700 wp_mkdir_p($files_dirname);
702 $writableDirectories = array($files_dirname);
705 foreach ($writableDirectories as $dir) {
706 $dirName = CIVICRM_WINDOWS ?
$dir : CIVICRM_DIRECTORY_SEPARATOR
. $dir;
707 $testDetails = array(
708 ts("File permissions"),
709 ts("Is the %1 folder writeable?", array(1 => $dir)),
712 $this->requireWriteable($dirName, $testDetails, TRUE);
715 // Check for rewriting
716 if (isset($_SERVER['SERVER_SOFTWARE'])) {
717 $webserver = strip_tags(trim($_SERVER['SERVER_SOFTWARE']));
719 elseif (isset($_SERVER['SERVER_SIGNATURE'])) {
720 $webserver = strip_tags(trim($_SERVER['SERVER_SIGNATURE']));
723 if ($webserver == '') {
724 $webserver = ts("I can't tell what webserver you are running");
727 // Check for $_SERVER configuration
728 $this->requireServerVariables(array('SCRIPT_NAME', 'HTTP_HOST', 'SCRIPT_FILENAME'), array(
729 ts("Webserver config"),
730 ts("Recognised webserver"),
731 ts("You seem to be using an unsupported webserver. The server variables SCRIPT_NAME, HTTP_HOST, SCRIPT_FILENAME need to be set."),
734 // Check for MySQL support
735 $this->requireFunction('mysqli_connect', array(
736 ts("PHP Configuration"),
738 ts("MySQL support not included in PHP."),
741 // Check for XML support
742 $this->requireFunction('simplexml_load_file', array(
743 ts("PHP Configuration"),
744 ts("SimpleXML support"),
745 ts("SimpleXML support not included in PHP."),
748 // Check for JSON support
749 $this->requireFunction('json_encode', array(
750 ts("PHP Configuration"),
752 ts("JSON support not included in PHP."),
755 // check for Multibyte support such as mb_substr. Required for proper handling of Multilingual setups.
756 $this->requireFunction('mb_substr', array(
757 ts("PHP Configuration"),
758 ts("Multibyte support"),
759 ts("Multibyte support not enabled in PHP."),
762 // Check for xcache_isset and emit warning if exists
763 $this->checkXCache(array(
764 ts("PHP Configuration"),
765 ts("XCache compatibility"),
766 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."),
769 // Check memory allocation
770 $this->requireMemory(32 * 1024 * 1024,
773 ts("PHP Configuration"),
774 ts("Memory allocated (PHP config option 'memory_limit')"),
775 ts("CiviCRM needs a minimum of %1 MB allocated to PHP, but recommends %2 MB.", array(1 => 32, 2 => 64)),
776 ini_get("memory_limit"),
780 return $this->errors
;
785 * @param $recommended
786 * @param $testDetails
788 public function requireMemory($min, $recommended, $testDetails) {
789 $this->testing($testDetails);
790 $mem = $this->getPHPMemory();
792 if ($mem < $min && $mem > 0) {
793 $testDetails[2] .= " " . ts("You only have %1 allocated", array(1 => ini_get("memory_limit")));
794 $this->error($testDetails);
796 elseif ($mem < $recommended && $mem > 0) {
797 $testDetails[2] .= " " . ts("You only have %1 allocated", array(1 => ini_get("memory_limit")));
798 $this->warning($testDetails);
801 $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));
802 $this->warning($testDetails);
809 public function getPHPMemory() {
810 $memString = ini_get("memory_limit");
812 switch (strtolower(substr($memString, -1))) {
814 return round(substr($memString, 0, -1) * 1024);
817 return round(substr($memString, 0, -1) * 1024 * 1024);
820 return round(substr($memString, 0, -1) * 1024 * 1024 * 1024);
823 return round($memString);
827 public function listErrors() {
829 echo "<p>" . ts("The following problems are preventing me from installing CiviCRM:") . "</p>";
830 foreach ($this->errors
as $error) {
831 echo "<li>" . htmlentities($error) . "</li>";
837 * @param null $section
839 public function showTable($section = NULL) {
841 $tests = $this->tests
[$section];
842 echo "<table class=\"testResults\" width=\"100%\">";
843 foreach ($tests as $test => $result) {
844 echo "<tr class=\"$result[0]\"><td>$test</td><td>" . nl2br(htmlentities($result[1])) . "</td></tr>";
849 foreach ($this->tests
as $section => $tests) {
850 echo "<h3>$section</h3>";
851 echo "<table class=\"testResults\" width=\"100%\">";
853 foreach ($tests as $test => $result) {
854 echo "<tr class=\"$result[0]\"><td>$test</td><td>" . nl2br(htmlentities($result[1])) . "</td></tr>";
862 * @param string $funcName
863 * @param $testDetails
867 public function requireFunction($funcName, $testDetails) {
868 $this->testing($testDetails);
870 if (!function_exists($funcName)) {
871 $this->error($testDetails);
880 * @param $testDetails
882 public function checkXCache($testDetails) {
883 if (function_exists('xcache_isset') &&
884 ini_get('xcache.size') > 0
886 $this->testing($testDetails);
887 $this->warning($testDetails);
892 * @param array $testDetails
895 public function requirePHPVersion($testDetails) {
897 $this->testing($testDetails);
899 $phpVersion = phpversion();
900 $aboveMinVersion = version_compare($phpVersion, CRM_Upgrade_Incremental_General
::MIN_INSTALL_PHP_VER
) >= 0;
902 if ($aboveMinVersion) {
903 if (version_compare($phpVersion, CRM_Upgrade_Incremental_General
::MIN_RECOMMENDED_PHP_VER
) < 0) {
904 $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 2 => CRM_Upgrade_Incremental_General
::MIN_RECOMMENDED_PHP_VER
,
907 3 => CRM_Upgrade_Incremental_General
::RECOMMENDED_PHP_VER
,
909 $this->warning($testDetails);
914 if (empty($testDetails[2])) {
915 $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));
918 $this->error($testDetails);
922 * @param string $filename
923 * @param $testDetails
924 * @param bool $absolute
926 public function requireFile($filename, $testDetails, $absolute = FALSE) {
927 $this->testing($testDetails);
929 $filename = $this->getBaseDir() . $filename;
931 if (!file_exists($filename)) {
932 $testDetails[2] .= " (" . ts("file '%1' not found", array(1 => $filename)) . ')';
933 $this->error($testDetails);
938 * @param $testDetails
940 public function requireNoPathSeparator($testDetails) {
941 $this->testing($testDetails);
942 if (substr_count($this->getBaseDir(), PATH_SEPARATOR
)) {
943 $this->error($testDetails);
948 * @param string $filename
949 * @param $testDetails
951 public function requireNoFile($filename, $testDetails) {
952 $this->testing($testDetails);
953 $filename = $this->getBaseDir() . $filename;
954 if (file_exists($filename)) {
955 $testDetails[2] .= " (" . ts("file '%1' found", array(1 => $filename)) . ")";
956 $this->error($testDetails);
961 * @param string $filename
962 * @param $testDetails
964 public function moveFileOutOfTheWay($filename, $testDetails) {
965 $this->testing($testDetails);
966 $filename = $this->getBaseDir() . $filename;
967 if (file_exists($filename)) {
968 if (file_exists("$filename.bak")) {
971 rename($filename, "$filename.bak");
976 * @param string $filename
977 * @param $testDetails
978 * @param bool $absolute
980 public function requireWriteable($filename, $testDetails, $absolute = FALSE) {
981 $this->testing($testDetails);
983 $filename = $this->getBaseDir() . $filename;
986 if (!is_writable($filename)) {
988 if (function_exists('posix_getpwuid')) {
989 $user = posix_getpwuid(posix_geteuid());
990 $name = '- ' . $user['name'] . ' -';
993 if (!isset($testDetails[2])) {
994 $testDetails[2] = NULL;
996 $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";
997 $this->error($testDetails);
1002 * @param string $moduleName
1003 * @param $testDetails
1005 public function requireApacheModule($moduleName, $testDetails) {
1006 $this->testing($testDetails);
1007 if (!in_array($moduleName, apache_get_modules())) {
1008 $this->error($testDetails);
1014 * @param string $username
1016 * @param $testDetails
1018 public function requireMysqlConnection($server, $username, $password, $testDetails) {
1019 $this->testing($testDetails);
1020 $this->conn
= $this->connect($server, $username, $password);
1026 $testDetails[2] .= ": " . mysqli_connect_error();
1027 $this->error($testDetails);
1033 * @param $testDetails
1035 public function requireMySQLServer($server, $testDetails) {
1036 $this->testing($testDetails);
1037 $conn = $this->connect($server, NULL, NULL);
1039 if ($conn ||
mysqli_connect_errno() < 2000) {
1043 $testDetails[2] .= ": " . mysqli_connect_error();
1044 $this->error($testDetails);
1050 * @param $testDetails
1052 public function requireMySQLVersion($version, $testDetails) {
1053 $this->testing($testDetails);
1055 if (!mysqli_get_server_info($this->conn
)) {
1056 $testDetails[2] = ts('Cannot determine the version of MySQL installed. Please ensure at least version %1 is installed.', array(1 => $version));
1057 $this->warning($testDetails);
1060 list($majorRequested, $minorRequested) = explode('.', $version);
1061 list($majorHas, $minorHas) = explode('.', mysqli_get_server_info($this->conn
));
1063 if (($majorHas > $majorRequested) ||
($majorHas == $majorRequested && $minorHas >= $minorRequested)) {
1067 $testDetails[2] .= "{$majorHas}.{$minorHas}.";
1068 $this->error($testDetails);
1075 * @param string $username
1078 * @param $testDetails
1080 public function requireMySQLInnoDB($server, $username, $password, $database, $testDetails) {
1081 $this->testing($testDetails);
1082 $conn = $this->connect($server, $username, $password);
1084 $testDetails[2] .= ' ' . ts("Could not determine if MySQL has InnoDB support. Assuming no.");
1085 $this->error($testDetails);
1089 $innodb_support = FALSE;
1090 $result = mysqli_query($conn, "SHOW ENGINES");
1091 while ($values = mysqli_fetch_array($result)) {
1092 if ($values['Engine'] == 'InnoDB') {
1093 if (strtolower($values['Support']) == 'yes' ||
1094 strtolower($values['Support']) == 'default'
1096 $innodb_support = TRUE;
1100 if ($innodb_support) {
1101 $testDetails[3] = ts('MySQL server does have InnoDB support');
1104 $testDetails[2] .= ' ' . ts('Could not determine if MySQL has InnoDB support. Assuming no');
1110 * @param string $username
1113 * @param $testDetails
1115 public function requireMySQLTempTables($server, $username, $password, $database, $testDetails) {
1116 $this->testing($testDetails);
1117 $conn = $this->connect($server, $username, $password);
1119 $testDetails[2] = ts('Could not login to the database.');
1120 $this->error($testDetails);
1124 if (!@mysqli_select_db
($conn, $database)) {
1125 $testDetails[2] = ts('Could not select the database.');
1126 $this->error($testDetails);
1130 $result = mysqli_query($conn, 'CREATE TEMPORARY TABLE civicrm_install_temp_table_test (test text)');
1132 $testDetails[2] = ts('Could not create a temp table.');
1133 $this->error($testDetails);
1135 $result = mysqli_query($conn, 'DROP TEMPORARY TABLE civicrm_install_temp_table_test');
1140 * @param string $username
1143 * @param $testDetails
1145 public function requireMySQLTrigger($server, $username, $password, $database, $testDetails) {
1146 $this->testing($testDetails);
1147 $conn = $this->connect($server, $username, $password);
1149 $testDetails[2] = ts('Could not login to the database.');
1150 $this->error($testDetails);
1154 if (!@mysqli_select_db
($conn, $database)) {
1155 $testDetails[2] = ts('Could not select the database.');
1156 $this->error($testDetails);
1160 $result = mysqli_query($conn, 'CREATE TABLE civicrm_install_temp_table_test (test text)');
1162 $testDetails[2] = ts('Could not create a table in the database.');
1163 $this->error($testDetails);
1166 $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');
1168 mysqli_query($conn, 'DROP TABLE civicrm_install_temp_table_test');
1169 $testDetails[2] = ts('Could not create a database trigger.');
1170 $this->error($testDetails);
1173 mysqli_query($conn, 'DROP TRIGGER civicrm_install_temp_table_test_trigger');
1174 mysqli_query($conn, 'DROP TABLE civicrm_install_temp_table_test');
1179 * @param string $username
1182 * @param $testDetails
1184 public function requireMySQLLockTables($server, $username, $password, $database, $testDetails) {
1185 $this->testing($testDetails);
1186 $conn = $this->connect($server, $username, $password);
1188 $testDetails[2] = ts('Could not connect to the database server.');
1189 $this->error($testDetails);
1193 if (!@mysqli_select_db
($conn, $database)) {
1194 $testDetails[2] = ts('Could not select the database.');
1195 $this->error($testDetails);
1199 $result = mysqli_query($conn, 'CREATE TEMPORARY TABLE civicrm_install_temp_table_test (test text)');
1201 $testDetails[2] = ts('Could not create a table in the database.');
1202 $this->error($testDetails);
1206 $result = mysqli_query($conn, 'LOCK TABLES civicrm_install_temp_table_test WRITE');
1208 $testDetails[2] = ts('Could not obtain a write lock for the database table.');
1209 $this->error($testDetails);
1210 $result = mysqli_query($conn, 'DROP TEMPORARY TABLE civicrm_install_temp_table_test');
1214 $result = mysqli_query($conn, 'UNLOCK TABLES');
1216 $testDetails[2] = ts('Could not release the lock for the database table.');
1217 $this->error($testDetails);
1218 $result = mysqli_query($conn, 'DROP TEMPORARY TABLE civicrm_install_temp_table_test');
1222 $result = mysqli_query($conn, 'DROP TEMPORARY TABLE civicrm_install_temp_table_test');
1227 * @param string $username
1229 * @param $testDetails
1231 public function requireMySQLAutoIncrementIncrementOne($server, $username, $password, $testDetails) {
1232 $this->testing($testDetails);
1233 $conn = $this->connect($server, $username, $password);
1235 $testDetails[2] = ts('Could not connect to the database server.');
1236 $this->error($testDetails);
1240 $result = mysqli_query($conn, "SHOW variables like 'auto_increment_increment'");
1242 $testDetails[2] = ts('Could not query database server variables.');
1243 $this->error($testDetails);
1247 $values = mysqli_fetch_row($result);
1248 if ($values[1] == 1) {
1249 $testDetails[3] = ts('MySQL server auto_increment_increment is 1');
1252 $this->error($testDetails);
1259 * @param string $username
1262 * @param $minValueKB
1263 * @param $testDetails
1265 public function requireMySQLThreadStack($server, $username, $password, $database, $minValueKB, $testDetails) {
1266 $this->testing($testDetails);
1267 $conn = $this->connect($server, $username, $password);
1269 $testDetails[2] = ts('Could not connect to the database server.');
1270 $this->error($testDetails);
1274 if (!@mysqli_select_db
($conn, $database)) {
1275 $testDetails[2] = ts('Could not select the database.');
1276 $this->error($testDetails);
1281 $result = mysqli_query($conn, "SHOW VARIABLES LIKE 'thread_stack'");
1283 $testDetails[2] = ts('Could not get information about the thread_stack of the database.');
1284 $this->error($testDetails);
1287 $values = mysqli_fetch_row($result);
1288 if ($values[1] < (1024 * $minValueKB)) {
1289 $testDetails[2] = ts('MySQL "thread_stack" is %1 kb', array(1 => ($values[1] / 1024)));
1290 $this->error($testDetails);
1300 * @param $testDetails
1302 public function requireNoExistingData(
1309 $this->testing($testDetails);
1310 $conn = $this->connect($server, $username, $password);
1312 @mysqli_select_db
($conn, $database);
1313 $contactRecords = mysqli_query($conn, "SELECT count(*) as contactscount FROM civicrm_contact");
1314 if ($contactRecords) {
1315 $contactRecords = mysqli_fetch_object($contactRecords);
1316 if ($contactRecords->contactscount
> 0) {
1317 $this->error($testDetails);
1322 $testDetails[3] = ts('CiviCRM data from previous installation does not exist in %1.', array(1 => $database));
1323 $this->testing($testDetails);
1328 * @param string $username
1331 * @param $testDetails
1332 * @param bool $onlyRequire
1334 public function requireDatabaseOrCreatePermissions(
1340 $onlyRequire = FALSE
1342 $this->testing($testDetails);
1343 $conn = $this->connect($server, $username, $password);
1346 if (@mysqli_select_db
($conn, $database)) {
1347 $okay = "Database '$database' exists";
1349 elseif ($onlyRequire) {
1350 $testDetails[2] = ts("The database: '%1' does not exist.", array(1 => $database));
1351 $this->error($testDetails);
1355 $query = sprintf("CREATE DATABASE %s", mysqli_real_escape_string($conn, $database));
1356 if (@mysqli_query
($conn, $query)) {
1357 $okay = ts("Able to create a new database.");
1360 $testDetails[2] .= " (" . ts("user '%1' doesn't have CREATE DATABASE permissions.", array(1 => $username)) . ")";
1361 $this->error($testDetails);
1367 $testDetails[3] = $okay;
1368 $this->testing($testDetails);
1374 * @param $errorMessage
1376 public function requireServerVariables($varNames, $errorMessage) {
1377 //$this->testing($testDetails);
1378 foreach ($varNames as $varName) {
1379 if (!$_SERVER[$varName]) {
1380 $missing[] = '$_SERVER[' . $varName . ']';
1383 if (!isset($missing)) {
1387 $testDetails[2] = " (" . ts('the following PHP variables are missing: %1', array(1 => implode(", ", $missing))) . ")";
1388 $this->error($testDetails);
1394 * @param string $username
1397 * @param $testDetails
1399 public function requireMysqlUtf8mb4($server, $username, $password, $database, $testDetails) {
1400 $this->testing($testDetails);
1401 $conn = $this->connect($server, $username, $password);
1403 $testDetails[2] = ts('Could not connect to the database server.');
1404 $this->error($testDetails);
1408 if (!@mysqli_select_db
($conn, $database)) {
1409 $testDetails[2] = ts('Could not select the database.');
1410 $this->error($testDetails);
1414 $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');
1416 $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');
1417 $this->warning($testDetails);
1420 $result = mysqli_query($conn, 'DROP TABLE civicrm_utf8mb4_test');
1422 // Ensure that the MySQL driver supports utf8mb4 encoding.
1423 $version = mysqli_get_client_info();
1424 if (strpos($version, 'mysqlnd') !== FALSE) {
1425 // The mysqlnd driver supports utf8mb4 starting at version 5.0.9.
1426 $version = preg_replace('/^\D+([\d.]+).*/', '$1', $version);
1427 if (version_compare($version, '5.0.9', '<')) {
1428 $testDetails[2] = 'It is recommended, though not yet required, to upgrade your PHP MySQL driver (mysqlnd) to >= 5.0.9 for utf8mb4 support.';
1429 $this->warning($testDetails);
1434 // The libmysqlclient driver supports utf8mb4 starting at version 5.5.3.
1435 if (version_compare($version, '5.5.3', '<')) {
1436 $testDetails[2] = 'It is recommended, though not yet required, to upgrade your PHP MySQL driver (libmysqlclient) to >= 5.5.3 for utf8mb4 support.';
1437 $this->warning($testDetails);
1444 * @param $testDetails
1448 public function isRunningApache($testDetails) {
1449 $this->testing($testDetails);
1450 if (function_exists('apache_get_modules') ||
stristr($_SERVER['SERVER_SIGNATURE'], 'Apache')) {
1454 $this->warning($testDetails);
1461 public function getBaseDir() {
1462 return dirname($_SERVER['SCRIPT_FILENAME']) . CIVICRM_DIRECTORY_SEPARATOR
;
1466 * @param $testDetails
1468 public function testing($testDetails) {
1469 if (!$testDetails) {
1473 $section = $testDetails[0];
1474 $test = $testDetails[1];
1476 $message = ts("OK");
1477 if (isset($testDetails[3])) {
1478 $message .= " ($testDetails[3])";
1481 $this->tests
[$section][$test] = array("good", $message);
1485 * @param $testDetails
1487 public function error($testDetails) {
1488 $section = $testDetails[0];
1489 $test = $testDetails[1];
1491 $this->tests
[$section][$test] = array("error", $testDetails[2]);
1492 $this->errors
[] = $testDetails;
1496 * @param $testDetails
1498 public function warning($testDetails) {
1499 $section = $testDetails[0];
1500 $test = $testDetails[1];
1502 $this->tests
[$section][$test] = array("warning", $testDetails[2]);
1503 $this->warnings
[] = $testDetails;
1509 public function hasErrors() {
1510 return !empty($this->errors
);
1516 public function hasWarnings() {
1517 return !empty($this->warnings
);
1525 class Installer
extends InstallRequirements
{
1533 public function createDatabaseIfNotExists($server, $username, $password, $database) {
1534 $conn = $this->connect($server, $username, $password);
1536 if (@mysqli_select_db
($conn, $database)) {
1537 // skip if database already present
1540 $query = sprintf("CREATE DATABASE %s", mysqli_real_escape_string($conn, $database));
1541 if (@mysqli_query
($conn, $query)) {
1544 $errorTitle = ts("Oops! Could not create database %1", array(1 => $database));
1545 $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.");
1546 errorDisplayPage($errorTitle, $errorMsg);
1555 public function install($config) {
1556 global $installDirPath;
1558 // create database if does not exists
1559 $this->createDatabaseIfNotExists($config['mysql']['server'],
1560 $config['mysql']['username'],
1561 $config['mysql']['password'],
1562 $config['mysql']['database']
1565 global $installDirPath;
1568 require_once $installDirPath . 'civicrm.php';
1569 civicrm_main($config);
1571 if (!$this->errors
) {
1572 global $installType, $installURLPath;
1574 $registerSiteURL = "https://civicrm.org/register-site";
1575 $commonOutputMessage
1576 = "<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>"
1577 . "<li>" . ts("We have integrated KCFinder with CKEditor and TinyMCE. This allows a user to upload images. All uploaded images are public.") . "</li>";
1582 $installType == 'drupal' &&
1583 version_compare(VERSION
, '7.0-rc1') >= 0
1589 $output .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
1590 $output .= '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">';
1591 $output .= '<head>';
1592 $output .= '<title>' . ts('CiviCRM Installed') . '</title>';
1593 $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
1594 $output .= '<link rel="stylesheet" type="text/css" href="template.css" />';
1595 $output .= '</head>';
1596 $output .= '<body>';
1597 $output .= '<div style="padding: 1em;"><p class="good">' . ts('CiviCRM has been successfully installed') . '</p>';
1600 $drupalURL = civicrm_cms_base();
1601 $drupalPermissionsURL = "{$drupalURL}index.php?q=admin/people/permissions";
1602 $drupalURL .= "index.php?q=civicrm/admin/configtask&reset=1";
1604 $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>";
1605 $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>";
1606 $output .= $commonOutputMessage;
1608 // automatically enable CiviCRM module once it is installed successfully.
1609 // so we need to Bootstrap Drupal, so that we can call drupal hooks.
1610 global $cmsPath, $crmPath;
1612 // relative / abosolute paths are not working for drupal, hence using chdir()
1615 // Force the re-initialisation of the config singleton on the next call
1616 // since so far, we had used the Config object without loading the DB.
1617 $c = CRM_Core_Config
::singleton(FALSE);
1620 include_once "./includes/bootstrap.inc";
1621 include_once "./includes/unicode.inc";
1623 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL
);
1625 // prevent session information from being saved.
1626 drupal_save_session(FALSE);
1628 // Force the current user to anonymous.
1629 $original_user = $GLOBALS['user'];
1630 $GLOBALS['user'] = drupal_anonymous_user();
1632 // explicitly setting error reporting, since we cannot handle drupal related notices
1635 // rebuild modules, so that civicrm is added
1636 system_rebuild_module_data();
1638 // now enable civicrm module.
1639 module_enable(array('civicrm', 'civicrmtheme'));
1641 // SystemInstallEvent will be called from here with the first call of CRM_Core_Config,
1642 // which calls Core_BAO_ConfigSetting::applyLocale(), who will default to calling
1643 // Civi::settings()->get('lcMessages');
1644 // Therefore, we need to pass the seedLanguage before that.
1645 global $civicrm_setting;
1646 $civicrm_setting['domain']['lcMessages'] = $config['seedLanguage'];
1648 // clear block, page, theme, and hook caches
1649 drupal_flush_all_caches();
1651 //add basic drupal permissions
1652 civicrm_install_set_drupal_perms();
1654 // restore the user.
1655 $GLOBALS['user'] = $original_user;
1656 drupal_save_session(TRUE);
1659 $output .= '</div>';
1660 $output .= '</body>';
1661 $output .= '</html>';
1665 $installType == 'backdrop'
1671 $output .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
1672 $output .= '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">';
1673 $output .= '<head>';
1674 $output .= '<title>' . ts('CiviCRM Installed') . '</title>';
1675 $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
1676 $output .= '<link rel="stylesheet" type="text/css" href="template.css" />';
1677 $output .= '</head>';
1678 $output .= '<body>';
1679 $output .= '<div style="padding: 1em;"><p class="good">' . ts('CiviCRM has been successfully installed') . '</p>';
1682 $backdropURL = civicrm_cms_base();
1683 $backdropPermissionsURL = "{$backdropURL}index.php?q=admin/config/people/permissions";
1684 $backdropURL .= "index.php?q=civicrm/admin/configtask&reset=1";
1686 $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>";
1687 $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>";
1688 $output .= $commonOutputMessage;
1690 // automatically enable CiviCRM module once it is installed successfully.
1691 // so we need to Bootstrap Drupal, so that we can call drupal hooks.
1692 global $cmsPath, $crmPath;
1694 // relative / abosolute paths are not working for drupal, hence using chdir()
1697 // Force the re-initialisation of the config singleton on the next call
1698 // since so far, we had used the Config object without loading the DB.
1699 $c = CRM_Core_Config
::singleton(FALSE);
1702 include_once "./core/includes/bootstrap.inc";
1703 include_once "./core/includes/unicode.inc";
1704 include_once "./core/includes/config.inc";
1706 backdrop_bootstrap(BACKDROP_BOOTSTRAP_FULL
);
1708 // prevent session information from being saved.
1709 backdrop_save_session(FALSE);
1711 // Force the current user to anonymous.
1712 $original_user = $GLOBALS['user'];
1713 $GLOBALS['user'] = backdrop_anonymous_user();
1715 // explicitly setting error reporting, since we cannot handle drupal related notices
1718 // rebuild modules, so that civicrm is added
1719 system_rebuild_module_data();
1721 // now enable civicrm module.
1722 module_enable(array('civicrm', 'civicrmtheme'));
1724 // clear block, page, theme, and hook caches
1725 backdrop_flush_all_caches();
1727 //add basic backdrop permissions
1728 civicrm_install_set_backdrop_perms();
1730 // restore the user.
1731 $GLOBALS['user'] = $original_user;
1732 backdrop_save_session(TRUE);
1734 //change the default language to one chosen
1735 if (isset($config['seedLanguage']) && $config['seedLanguage'] != 'en_US') {
1736 civicrm_api3('Setting', 'create', array(
1737 'domain_id' => 'current_domain',
1738 'lcMessages' => $config['seedLanguage'],
1743 $output .= '</div>';
1744 $output .= '</body>';
1745 $output .= '</html>';
1748 elseif ($installType == 'drupal' && version_compare(VERSION
, '6.0') >= 0) {
1752 $output .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
1753 $output .= '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">';
1754 $output .= '<head>';
1755 $output .= '<title>' . ts('CiviCRM Installed') . '</title>';
1756 $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
1757 $output .= '<link rel="stylesheet" type="text/css" href="template.css" />';
1758 $output .= '</head>';
1759 $output .= '<body>';
1760 $output .= '<div style="padding: 1em;"><p class="good">' . ts("CiviCRM has been successfully installed") . '</p>';
1763 $drupalURL = civicrm_cms_base();
1764 $drupalPermissionsURL = "{$drupalURL}index.php?q=admin/user/permissions";
1765 $drupalURL .= "index.php?q=civicrm/admin/configtask&reset=1";
1767 $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>";
1768 $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>";
1769 $output .= $commonOutputMessage;
1771 // explicitly setting error reporting, since we cannot handle drupal related notices
1774 // automatically enable CiviCRM module once it is installed successfully.
1775 // so we need to Bootstrap Drupal, so that we can call drupal hooks.
1776 global $cmsPath, $crmPath;
1778 // relative / abosolute paths are not working for drupal, hence using chdir()
1781 // Force the re-initialisation of the config singleton on the next call
1782 // since so far, we had used the Config object without loading the DB.
1783 $c = CRM_Core_Config
::singleton(FALSE);
1786 include_once "./includes/bootstrap.inc";
1787 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL
);
1789 // rebuild modules, so that civicrm is added
1790 module_rebuild_cache();
1792 // now enable civicrm module.
1793 module_enable(array('civicrm'));
1795 // clear block, page, theme, and hook caches
1796 drupal_flush_all_caches();
1798 //add basic drupal permissions
1799 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)');
1803 elseif ($installType == 'wordpress') {
1804 echo '<h1>' . ts('CiviCRM Installed') . '</h1>';
1805 echo '<div style="padding: 1em;"><p style="background-color: #0C0; border: 1px #070 solid; color: white;">' . ts("CiviCRM has been successfully installed") . '</p>';
1808 $cmsURL = civicrm_cms_base();
1809 $cmsURL .= "wp-admin/admin.php?page=CiviCRM&q=civicrm/admin/configtask&reset=1";
1810 $wpPermissionsURL = "wp-admin/admin.php?page=CiviCRM&q=civicrm/admin/access/wp-permissions&reset=1";
1812 $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>";
1813 $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>";
1814 $output .= $commonOutputMessage;
1817 $output .= '</div>';
1820 $c = CRM_Core_Config
::singleton(FALSE);
1822 $wpInstallRedirect = admin_url('admin.php?page=CiviCRM&q=civicrm&reset=1');
1824 window.location = '$wpInstallRedirect';
1829 return $this->errors
;
1834 function civicrm_install_set_drupal_perms() {
1835 if (!function_exists('db_select')) {
1836 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)');
1840 'access all custom data',
1841 'access uploaded files',
1842 'make online contributions',
1846 'register for events',
1848 'view event participants',
1849 'access CiviMail subscribe/unsubscribe pages',
1852 // Adding a permission that has not yet been assigned to a module by
1853 // a hook_permission implementation results in a database error.
1855 $allPerms = array_keys(module_invoke_all('permission'));
1856 foreach (array_diff($perms, $allPerms) as $perm) {
1858 'Cannot grant the %perm permission because it does not yet exist.',
1859 array('%perm' => $perm),
1863 $perms = array_intersect($perms, $allPerms);
1864 user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID
, $perms);
1865 user_role_grant_permissions(DRUPAL_ANONYMOUS_RID
, $perms);
1869 function civicrm_install_set_backdrop_perms() {
1871 'access all custom data',
1872 'access uploaded files',
1873 'make online contributions',
1877 'register for events',
1879 'view event participants',
1880 'access CiviMail subscribe/unsubscribe pages',
1883 // Adding a permission that has not yet been assigned to a module by
1884 // a hook_permission implementation results in a database error.
1886 $allPerms = array_keys(module_invoke_all('permission'));
1887 foreach (array_diff($perms, $allPerms) as $perm) {
1889 'Cannot grant the %perm permission because it does not yet exist.',
1890 array('%perm' => $perm),
1894 $perms = array_intersect($perms, $allPerms);
1895 user_role_grant_permissions(BACKDROP_AUTHENTICATED_ROLE
, $perms);
1896 user_role_grant_permissions(BACKDROP_ANONYMOUS_ROLE
, $perms);
1905 function getSiteDir($cmsPath, $str) {
1906 static $siteDir = '';
1912 $sites = CIVICRM_DIRECTORY_SEPARATOR
. 'sites' . CIVICRM_DIRECTORY_SEPARATOR
;
1913 $modules = CIVICRM_DIRECTORY_SEPARATOR
. 'modules' . CIVICRM_DIRECTORY_SEPARATOR
;
1914 preg_match("/" . preg_quote($sites, CIVICRM_DIRECTORY_SEPARATOR
) .
1915 "([\-a-zA-Z0-9_.]+)" .
1916 preg_quote($modules, CIVICRM_DIRECTORY_SEPARATOR
) . "/",
1917 $_SERVER['SCRIPT_FILENAME'], $matches
1919 $siteDir = isset($matches[1]) ?
$matches[1] : 'default';
1921 if (strtolower($siteDir) == 'all') {
1922 // For this case - use drupal's way of finding out multi-site directory
1923 $uri = explode(CIVICRM_DIRECTORY_SEPARATOR
, $_SERVER['SCRIPT_FILENAME']);
1924 $server = explode('.', implode('.', array_reverse(explode(':', rtrim($_SERVER['HTTP_HOST'], '.')))));
1925 for ($i = count($uri) - 1; $i > 0; $i--) {
1926 for ($j = count($server); $j > 0; $j--) {
1927 $dir = implode('.', array_slice($server, -$j)) . implode('.', array_slice($uri, 0, $i));
1928 if (file_exists($cmsPath . CIVICRM_DIRECTORY_SEPARATOR
.
1929 'sites' . CIVICRM_DIRECTORY_SEPARATOR
. $dir
1936 $siteDir = 'default';
1943 * @param $errorTitle
1947 function errorDisplayPage($errorTitle, $errorMsg, $showRefer = TRUE) {
1949 // Add a link to the documentation
1951 if (is_callable(array('CRM_Utils_System', 'docURL2'))) {
1952 $docLink = CRM_Utils_System
::docURL2('Installation and Upgrades', FALSE, 'Installation Guide', NULL, NULL, "wiki");
1958 if (function_exists('ts')) {
1959 $errorMsg .= '<p>' . ts("Refer to the online documentation for more information: ") . $docLink . '</p>';
1962 $errorMsg .= '<p>' . 'Refer to the online documentation for more information: ' . $docLink . '</p>';
1966 include 'error.html';