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 if (isset($_POST['civicrm_install_type'])) {
67 $installType = $_POST['civicrm_install_type'];
69 elseif (isset($_GET['civicrm_install_type'])) {
70 $installType = strtolower($_GET['civicrm_install_type']);
73 // default value if not set
74 $installType = "drupal";
77 if ($installType == 'drupal' ||
$installType == 'backdrop') {
78 $crmPath = dirname(dirname($_SERVER['SCRIPT_FILENAME']));
79 $installDirPath = $installURLPath = '';
81 elseif ($installType == 'wordpress') {
82 $crmPath = WP_PLUGIN_DIR
. DIRECTORY_SEPARATOR
. 'civicrm' . DIRECTORY_SEPARATOR
. 'civicrm' . DIRECTORY_SEPARATOR
;
83 $installDirPath = WP_PLUGIN_DIR
. DIRECTORY_SEPARATOR
. 'civicrm' . DIRECTORY_SEPARATOR
. 'civicrm' . DIRECTORY_SEPARATOR
. 'install' . DIRECTORY_SEPARATOR
;
84 $installURLPath = WP_PLUGIN_URL
. DIRECTORY_SEPARATOR
. 'civicrm' . DIRECTORY_SEPARATOR
. 'civicrm' . DIRECTORY_SEPARATOR
. 'install' . DIRECTORY_SEPARATOR
;
87 $errorTitle = "Oops! Unsupported installation mode";
88 $errorMsg = sprintf('%s: unknown installation mode. Please refer to the online documentation for more information.', $installType);
89 errorDisplayPage($errorTitle, $errorMsg, FALSE);
92 $pkgPath = $crmPath . DIRECTORY_SEPARATOR
. 'packages';
94 require_once $crmPath . '/CRM/Core/ClassLoader.php';
95 CRM_Core_ClassLoader
::singleton()->register();
98 if (isset($_POST['loadGenerated'])) {
102 require_once dirname(__FILE__
) . CIVICRM_DIRECTORY_SEPARATOR
. 'langs.php';
103 foreach ($langs as $locale => $_) {
104 if ($locale == 'en_US') {
107 if (!file_exists(implode(CIVICRM_DIRECTORY_SEPARATOR
, array($crmPath, 'sql', "civicrm_data.$locale.mysql")))) {
108 unset($langs[$locale]);
113 // This is mostly sympbolic, since nothing we do during the install
114 // really requires CIVICRM_UF to be defined.
115 $installTypeToUF = array(
116 'wordpress' => 'WordPress',
117 'drupal' => 'Drupal',
118 'backdrop' => 'Backdrop',
121 $uf = (isset($installTypeToUF[$installType]) ?
$installTypeToUF[$installType] : 'Drupal');
122 define('CIVICRM_UF', $uf);
124 // Set the Locale (required by CRM_Core_Config)
128 $seedLanguage = 'en_US';
130 // CRM-16801 This validates that seedLanguage is valid by looking in $langs.
131 // NB: the variable is initial a $_REQUEST for the initial page reload,
132 // then becomes a $_POST when the installation form is submitted.
133 if (isset($_REQUEST['seedLanguage']) and isset($langs[$_REQUEST['seedLanguage']])) {
134 $seedLanguage = $_REQUEST['seedLanguage'];
135 $tsLocale = $_REQUEST['seedLanguage'];
138 $config = CRM_Core_Config
::singleton(FALSE);
139 $GLOBALS['civicrm_default_error_scope'] = NULL;
141 // The translation files are in the parent directory (l10n)
142 $i18n = CRM_Core_I18n
::singleton();
144 // Support for Arabic, Hebrew, Farsi, etc.
145 // Used in the template.html
146 $short_lang_code = CRM_Core_I18n_PseudoConstant
::shortForLong($tsLocale);
147 $text_direction = (CRM_Core_I18n
::isLanguageRTL($tsLocale) ?
'rtl' : 'ltr');
150 if ($installType == 'drupal') {
151 //CRM-6840 -don't force to install in sites/all/modules/
152 $object = new CRM_Utils_System_Drupal();
153 $cmsPath = $object->cmsRootPath();
155 $siteDir = getSiteDir($cmsPath, $_SERVER['SCRIPT_FILENAME']);
156 $alreadyInstalled = file_exists($cmsPath . CIVICRM_DIRECTORY_SEPARATOR
.
157 'sites' . CIVICRM_DIRECTORY_SEPARATOR
.
158 $siteDir . CIVICRM_DIRECTORY_SEPARATOR
.
159 'civicrm.settings.php'
162 elseif ($installType == 'backdrop') {
163 $object = new CRM_Utils_System_Backdrop();
164 $cmsPath = $object->cmsRootPath();
165 $siteDir = getSiteDir($cmsPath, $_SERVER['SCRIPT_FILENAME']);
166 $alreadyInstalled = file_exists($cmsPath . CIVICRM_DIRECTORY_SEPARATOR
. 'civicrm.settings.php');
168 elseif ($installType == 'wordpress') {
169 $cmsPath = WP_PLUGIN_DIR
. DIRECTORY_SEPARATOR
. 'civicrm';
170 $upload_dir = wp_upload_dir();
171 $files_dirname = $upload_dir['basedir'] . DIRECTORY_SEPARATOR
. 'civicrm';
172 $wp_civi_settings = $upload_dir['basedir'] . DIRECTORY_SEPARATOR
. 'civicrm' . DIRECTORY_SEPARATOR
. 'civicrm.settings.php';
173 $wp_civi_settings_deprectated = CIVICRM_PLUGIN_DIR
. 'civicrm.settings.php';
174 if (file_exists($wp_civi_settings_deprectated)) {
175 $alreadyInstalled = $wp_civi_settings_deprectated;
177 elseif (file_exists($wp_civi_settings)) {
178 $alreadyInstalled = $wp_civi_settings;
182 if ($installType == 'drupal') {
183 // Lets check only /modules/.
184 $pattern = '/' . preg_quote(CIVICRM_DIRECTORY_SEPARATOR
. 'modules', CIVICRM_DIRECTORY_SEPARATOR
) . '/';
186 if (!preg_match($pattern, str_replace("\\", "/", $_SERVER['SCRIPT_FILENAME']))) {
187 $directory = implode(CIVICRM_DIRECTORY_SEPARATOR
, array('sites', 'all', 'modules'));
188 $errorTitle = ts("Oops! Please correct your install location");
189 $errorMsg = ts("Please untar (uncompress) your downloaded copy of CiviCRM in the <strong>%1</strong> directory below your Drupal root directory.", array(1 => $directory));
190 errorDisplayPage($errorTitle, $errorMsg);
194 if ($installType == 'backdrop') {
195 // Lets check only /modules/.
196 $pattern = '/' . preg_quote(CIVICRM_DIRECTORY_SEPARATOR
. 'modules', CIVICRM_DIRECTORY_SEPARATOR
) . '/';
198 if (!preg_match($pattern, str_replace("\\", "/", $_SERVER['SCRIPT_FILENAME']))) {
199 $directory = 'modules';
200 $errorTitle = ts("Oops! Please correct your install location");
201 $errorMsg = ts("Please untar (uncompress) your downloaded copy of CiviCRM in the <strong>%1</strong> directory below your Drupal root directory.", array(1 => $directory));
202 errorDisplayPage($errorTitle, $errorMsg);
206 // Exit with error if CiviCRM has already been installed.
207 if ($alreadyInstalled) {
208 $errorTitle = ts("Oops! CiviCRM is already installed");
209 $settings_directory = $cmsPath;
211 if ($installType == 'drupal') {
212 $settings_directory = implode(CIVICRM_DIRECTORY_SEPARATOR
, array(
213 ts('[your Drupal root directory]'),
218 if ($installType == 'backdrop') {
219 $settings_directory = implode(CIVICRM_DIRECTORY_SEPARATOR
, array(
220 ts('[your Backdrop root directory]'),
225 $docLink = CRM_Utils_System
::docURL2('Installation and Upgrades', FALSE, ts('Installation Guide'), NULL, NULL, "wiki");
226 $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));
227 errorDisplayPage($errorTitle, $errorMsg, FALSE);
230 $versionFile = $crmPath . CIVICRM_DIRECTORY_SEPARATOR
. 'civicrm-version.php';
231 if (file_exists($versionFile)) {
232 require_once $versionFile;
233 $civicrm_version = civicrmVersion();
236 $civicrm_version = 'unknown';
239 if ($installType == 'drupal') {
240 // Ensure that they have downloaded the correct version of CiviCRM
241 if ($civicrm_version['cms'] != 'Drupal' && $civicrm_version['cms'] != 'Drupal6') {
242 $errorTitle = ts("Oops! Incorrect CiviCRM version");
243 $errorMsg = ts("This installer can only be used for the Drupal version of CiviCRM.");
244 errorDisplayPage($errorTitle, $errorMsg);
247 define('DRUPAL_ROOT', $cmsPath);
248 $drupalVersionFiles = array(
250 implode(CIVICRM_DIRECTORY_SEPARATOR
, array($cmsPath, 'modules', 'system', 'system.module')),
252 implode(CIVICRM_DIRECTORY_SEPARATOR
, array($cmsPath, 'includes', 'bootstrap.inc')),
254 foreach ($drupalVersionFiles as $drupalVersionFile) {
255 if (file_exists($drupalVersionFile)) {
256 require_once $drupalVersionFile;
260 // Bootstrap Drupal to get settings and user
261 $base_root = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ?
'https' : 'http';
262 $base_root .= '://' . $_SERVER['HTTP_HOST'];
263 $base_url = $base_root;
264 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL
);
266 // Check that user is logged in and has administrative permissions
267 // This is necessary because the script exposes the database settings in the form and these could be viewed by unauthorised users
268 if ((!function_exists('user_access')) ||
(!user_access('administer site configuration'))) {
269 $errorTitle = ts("You don't have permission to access this page");
270 $errorMsg = ts("The installer can only be run by a user with the permission to administer site configuration.");
271 errorDisplayPage($errorTitle, $errorMsg);
275 if (!defined('VERSION') or version_compare(VERSION
, '6.0') < 0) {
276 $errorTitle = ts("Oops! Incorrect Drupal version");
277 $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)));
278 errorDisplayPage($errorTitle, $errorMsg);
281 elseif ($installType == 'backdrop') {
282 // Ensure that they have downloaded the correct version of CiviCRM
283 if ($civicrm_version['cms'] != 'Backdrop') {
284 $errorTitle = ts("Oops! Incorrect CiviCRM version");
285 $errorMsg = ts("This installer can only be used for the Backdrop version of CiviCRM.");
286 errorDisplayPage($errorTitle, $errorMsg);
289 define('BACKDROP_ROOT', $cmsPath);
291 $backdropVersionFiles = array(
293 implode(CIVICRM_DIRECTORY_SEPARATOR
, array($cmsPath, 'core', 'includes', 'bootstrap.inc')),
295 foreach ($backdropVersionFiles as $backdropVersionFile) {
296 if (file_exists($backdropVersionFile)) {
297 require_once $backdropVersionFile;
300 if (!defined('BACKDROP_VERSION') or version_compare(BACKDROP_VERSION
, '1.0') < 0) {
301 $errorTitle = ts("Oops! Incorrect Backdrop version");
302 $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)));
303 errorDisplayPage($errorTitle, $errorMsg);
306 elseif ($installType == 'wordpress') {
308 $civicrm_version['cms'] = 'WordPress';
310 // Ensure that they have downloaded the correct version of CiviCRM
311 if ($civicrm_version['cms'] != 'WordPress') {
312 $errorTitle = ts("Oops! Incorrect CiviCRM version");
313 $errorMsg = ts("This installer can only be used for the WordPress version of CiviCRM.");
314 errorDisplayPage($errorTitle, $errorMsg);
318 // Load CiviCRM database config
319 if (isset($_POST['mysql'])) {
320 $databaseConfig = $_POST['mysql'];
323 if ($installType == 'wordpress') {
324 // Load WP database config
325 if (isset($_POST['mysql'])) {
326 $databaseConfig = $_POST['mysql'];
329 $databaseConfig = array(
331 "username" => DB_USER
,
332 "password" => DB_PASSWORD
,
333 "database" => DB_NAME
,
338 if ($installType == 'drupal') {
339 // Load drupal database config
340 if (isset($_POST['drupal'])) {
341 $drupalConfig = $_POST['drupal'];
344 $dbServer = $databases['default']['default']['host'];
345 if (!empty($databases['default']['default']['port'])) {
346 $dbServer .= ':' . $databases['default']['default']['port'];
348 $drupalConfig = array(
349 "server" => $dbServer,
350 "username" => $databases['default']['default']['username'],
351 "password" => $databases['default']['default']['password'],
352 "database" => $databases['default']['default']['database'],
357 if ($installType == 'backdrop') {
358 // Load backdrop database config
359 if (isset($_POST['backdrop'])) {
360 $backdropConfig = $_POST['backdrop'];
363 $backdropConfig = array(
364 "server" => "localhost",
365 "username" => "backdrop",
367 "database" => "backdrop",
372 // By default set CiviCRM database to be same as CMS database
373 if (!isset($databaseConfig)) {
374 if (($installType == 'drupal') && (isset($drupalConfig))) {
375 $databaseConfig = $drupalConfig;
377 if (($installType == 'backdrop') && (isset($backdropConfig))) {
378 $databaseConfig = $backdropConfig;
382 // Check requirements
383 $req = new InstallRequirements();
386 if ($req->hasErrors()) {
387 $hasErrorOtherThanDatabase = TRUE;
390 if ($databaseConfig) {
391 $dbReq = new InstallRequirements();
392 $dbReq->checkdatabase($databaseConfig, 'CiviCRM');
393 if ($installType == 'drupal') {
394 $dbReq->checkdatabase($drupalConfig, 'Drupal');
396 if ($installType == 'backdrop') {
397 $dbReq->checkdatabase($backdropConfig, 'Backdrop');
402 if (isset($_POST['go']) && !$req->hasErrors() && !$dbReq->hasErrors()) {
403 // Confirm before reinstalling
404 if (!isset($_POST['force_reinstall']) && $alreadyInstalled) {
405 include $installDirPath . 'template.html';
408 $inst = new Installer();
409 $inst->install($_POST);
412 // Show the config form
415 include $installDirPath . 'template.html';
419 * This class checks requirements
420 * Each of the requireXXX functions takes an argument which gives a user description of the test. It's an array
422 * $description[0] - The test category
423 * $description[1] - The test title
424 * $description[2] - The test error to show, if it goes wrong
426 class InstallRequirements
{
432 // @see CRM_Upgrade_Form::MINIMUM_THREAD_STACK
433 const MINIMUM_THREAD_STACK
= 192;
436 * Just check that the database configuration is okay.
437 * @param $databaseConfig
440 public function checkdatabase($databaseConfig, $dbName) {
441 if ($this->requireFunction('mysqli_connect',
443 ts("PHP Configuration"),
445 ts("MySQL support not included in PHP."),
449 $this->requireMySQLServer($databaseConfig['server'],
451 ts("MySQL %1 Configuration", array(1 => $dbName)),
452 ts("Does the server exist?"),
453 ts("Can't find the a MySQL server on '%1'.", array(1 => $databaseConfig['server'])),
454 $databaseConfig['server'],
457 if ($this->requireMysqlConnection($databaseConfig['server'],
458 $databaseConfig['username'],
459 $databaseConfig['password'],
461 ts("MySQL %1 Configuration", array(1 => $dbName)),
462 ts("Are the access credentials correct?"),
463 ts("That username/password doesn't work"),
467 @$this->requireMySQLVersion("5.1",
469 ts("MySQL %1 Configuration", array(1 => $dbName)),
470 ts("MySQL version at least %1", array(1 => '5.1')),
471 ts("MySQL version %1 or higher is required, you are running MySQL %2.", array(1 => '5.1', 2 => mysqli_get_server_info($this->conn
))),
472 ts("MySQL %1", array(1 => mysqli_get_server_info($this->conn
))),
475 $this->requireMySQLAutoIncrementIncrementOne($databaseConfig['server'],
476 $databaseConfig['username'],
477 $databaseConfig['password'],
479 ts("MySQL %1 Configuration", array(1 => $dbName)),
480 ts("Is auto_increment_increment set to 1"),
481 ts("An auto_increment_increment value greater than 1 is not currently supported. Please see issue CRM-7923 for further details and potential workaround."),
484 $testDetails = array(
485 ts("MySQL %1 Configuration", array(1 => $dbName)),
486 ts("Is the provided database name valid?"),
487 ts("The database name provided is not valid. Please use only 0-9, a-z, A-Z, _ and - as characters in the name."),
489 if (!CRM_Core_DAO
::requireSafeDBName($databaseConfig['database'])) {
490 $this->error($testDetails);
494 $this->testing($testDetails);
496 $this->requireMySQLThreadStack($databaseConfig['server'],
497 $databaseConfig['username'],
498 $databaseConfig['password'],
499 $databaseConfig['database'],
500 self
::MINIMUM_THREAD_STACK
,
502 ts("MySQL %1 Configuration", array(1 => $dbName)),
503 ts("Does MySQL thread_stack meet minimum (%1k)", array(1 => self
::MINIMUM_THREAD_STACK
)),
505 // "The MySQL thread_stack does not meet minimum " . CRM_Upgrade_Form::MINIMUM_THREAD_STACK . "k. Please update thread_stack in my.cnf.",
509 $onlyRequire = ($dbName == 'Drupal' ||
$dbName == 'Backdrop') ?
TRUE : FALSE;
510 $this->requireDatabaseOrCreatePermissions(
511 $databaseConfig['server'],
512 $databaseConfig['username'],
513 $databaseConfig['password'],
514 $databaseConfig['database'],
516 ts("MySQL %1 Configuration", array(1 => $dbName)),
517 ts("Can I access/create the database?"),
518 ts("I can't create new databases and the database '%1' doesn't exist.", array(1 => $databaseConfig['database'])),
522 if ($dbName != 'Drupal' && $dbName != 'Backdrop') {
523 $this->requireNoExistingData(
524 $databaseConfig['server'],
525 $databaseConfig['username'],
526 $databaseConfig['password'],
527 $databaseConfig['database'],
529 ts("MySQL %1 Configuration", array(1 => $dbName)),
530 ts("Does the database have data from a previous installation?"),
531 ts("CiviCRM data from previous installation exists in '%1'.", array(1 => $databaseConfig['database'])),
534 $this->requireMySQLInnoDB($databaseConfig['server'],
535 $databaseConfig['username'],
536 $databaseConfig['password'],
537 $databaseConfig['database'],
539 ts("MySQL %1 Configuration", array(1 => $dbName)),
540 ts("Can I access/create InnoDB tables in the database?"),
541 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."),
544 $this->requireMySQLTempTables($databaseConfig['server'],
545 $databaseConfig['username'],
546 $databaseConfig['password'],
547 $databaseConfig['database'],
549 ts("MySQL %1 Configuration", array(1 => $dbName)),
550 ts('Can I create temporary tables in the database?'),
551 ts('Unable to create temporary tables. This MySQL user is missing the CREATE TEMPORARY TABLES privilege.'),
554 $this->requireMySQLLockTables($databaseConfig['server'],
555 $databaseConfig['username'],
556 $databaseConfig['password'],
557 $databaseConfig['database'],
559 ts("MySQL %1 Configuration", array(1 => $dbName)),
560 ts('Can I create lock tables in the database?'),
561 ts('Unable to lock tables. This MySQL user is missing the LOCK TABLES privilege.'),
564 $this->requireMySQLTrigger($databaseConfig['server'],
565 $databaseConfig['username'],
566 $databaseConfig['password'],
567 $databaseConfig['database'],
569 ts("MySQL %1 Configuration", array(1 => $dbName)),
570 ts('Can I create triggers in the database?'),
571 ts('Unable to create triggers. This MySQL user is missing the CREATE TRIGGERS privilege.'),
574 $this->requireMySQLUtf8mb4($databaseConfig['server'],
575 $databaseConfig['username'],
576 $databaseConfig['password'],
577 $databaseConfig['database'],
579 ts("MySQL %1 Configuration", array(1 => $dbName)),
580 ts('Is the <code>utf8mb4</code> character set supported?'),
581 ts('This MySQL server does not support the <code>utf8mb4</code> character set.'),
589 * Connect via mysqli.
591 * This is exactly the same as mysqli_connect(), except that it accepts
592 * the port as part of the `$host`.
594 * @param string $host
595 * Ex: 'localhost', 'localhost:3307', '127.0.0.1:3307', '[::1]', '[::1]:3307'.
596 * @param string $username
597 * @param string $password
598 * @param string $database
601 protected function connect($host, $username, $password, $database = '') {
602 $hostParts = explode(':', $host);
603 if (count($hostParts) > 1 && strrpos($host, ']') !== strlen($host) - 1) {
604 $port = array_pop($hostParts);
605 $host = implode(':', $hostParts);
610 $conn = @mysqli_connect
($host, $username, $password, $database, $port);
615 * Check everything except the database.
617 public function check() {
618 global $crmPath, $installType;
620 $this->errors
= NULL;
622 $this->requirePHPVersion(array(
623 ts("PHP Configuration"),
624 ts("PHP5 installed"),
627 // Check that we can identify the root folder successfully
628 $this->requireFile($crmPath . CIVICRM_DIRECTORY_SEPARATOR
. 'README.md',
630 ts("File permissions"),
631 ts("Does the webserver know where files are stored?"),
632 ts("The webserver isn't letting me identify where files are stored."),
638 // CRM-6485: make sure the path does not contain PATH_SEPARATOR, as we don’t know how to escape it
639 $this->requireNoPathSeparator(
641 ts("File permissions"),
642 ts('Does the CiviCRM path contain PATH_SEPARATOR?'),
643 ts('The path %1 contains PATH_SEPARATOR (the %2 character).', array(1 => $this->getBaseDir(), 2 => PATH_SEPARATOR
)),
648 $requiredDirectories = array('CRM', 'packages', 'templates', 'js', 'api', 'i', 'sql');
649 foreach ($requiredDirectories as $dir) {
650 $this->requireFile($crmPath . CIVICRM_DIRECTORY_SEPARATOR
. $dir,
652 ts("File permissions"),
653 ts("Folder '%1' exists?", array(1 => $dir)),
654 ts("There is no '%1' folder.", array(1 => $dir)),
659 $configIDSiniDir = NULL;
661 $siteDir = getSiteDir($cmsPath, $_SERVER['SCRIPT_FILENAME']);
662 if ($installType == 'drupal') {
664 // make sure that we can write to sites/default and files/
665 $writableDirectories = array(
666 $cmsPath . CIVICRM_DIRECTORY_SEPARATOR
.
667 'sites' . CIVICRM_DIRECTORY_SEPARATOR
.
668 $siteDir . CIVICRM_DIRECTORY_SEPARATOR
.
670 $cmsPath . CIVICRM_DIRECTORY_SEPARATOR
.
671 'sites' . CIVICRM_DIRECTORY_SEPARATOR
.
675 elseif ($installType == 'backdrop') {
677 // make sure that we can write to sites/default and files/
678 $writableDirectories = array(
679 $cmsPath . CIVICRM_DIRECTORY_SEPARATOR
.
684 elseif ($installType == 'wordpress') {
685 // make sure that we can write to uploads/civicrm/
686 $upload_dir = wp_upload_dir();
687 $files_dirname = $upload_dir['basedir'] . DIRECTORY_SEPARATOR
. 'civicrm';
688 if (!file_exists($files_dirname)) {
689 wp_mkdir_p($files_dirname);
691 $writableDirectories = array($files_dirname);
694 foreach ($writableDirectories as $dir) {
695 $dirName = CIVICRM_WINDOWS ?
$dir : CIVICRM_DIRECTORY_SEPARATOR
. $dir;
696 $testDetails = array(
697 ts("File permissions"),
698 ts("Is the %1 folder writeable?", array(1 => $dir)),
701 $this->requireWriteable($dirName, $testDetails, TRUE);
704 //check for Config.IDS.ini, file may exist in re-install
705 $configIDSiniDir = array($cmsPath, 'sites', $siteDir, 'files', 'civicrm', 'upload', 'Config.IDS.ini');
707 if (is_array($configIDSiniDir) && !empty($configIDSiniDir)) {
708 $configIDSiniFile = implode(CIVICRM_DIRECTORY_SEPARATOR
, $configIDSiniDir);
709 if (file_exists($configIDSiniFile)) {
710 unlink($configIDSiniFile);
714 // Check for rewriting
715 if (isset($_SERVER['SERVER_SOFTWARE'])) {
716 $webserver = strip_tags(trim($_SERVER['SERVER_SOFTWARE']));
718 elseif (isset($_SERVER['SERVER_SIGNATURE'])) {
719 $webserver = strip_tags(trim($_SERVER['SERVER_SIGNATURE']));
722 if ($webserver == '') {
723 $webserver = ts("I can't tell what webserver you are running");
726 // Check for $_SERVER configuration
727 $this->requireServerVariables(array('SCRIPT_NAME', 'HTTP_HOST', 'SCRIPT_FILENAME'), array(
728 ts("Webserver config"),
729 ts("Recognised webserver"),
730 ts("You seem to be using an unsupported webserver. The server variables SCRIPT_NAME, HTTP_HOST, SCRIPT_FILENAME need to be set."),
733 // Check for MySQL support
734 $this->requireFunction('mysqli_connect', array(
735 ts("PHP Configuration"),
737 ts("MySQL support not included in PHP."),
740 // Check for XML support
741 $this->requireFunction('simplexml_load_file', array(
742 ts("PHP Configuration"),
743 ts("SimpleXML support"),
744 ts("SimpleXML support not included in PHP."),
747 // Check for JSON support
748 $this->requireFunction('json_encode', array(
749 ts("PHP Configuration"),
751 ts("JSON support not included in PHP."),
754 // check for Multibyte support such as mb_substr. Required for proper handling of Multilingual setups.
755 $this->requireFunction('mb_substr', array(
756 ts("PHP Configuration"),
757 ts("Multibyte support"),
758 ts("Multibyte support not enabled in PHP."),
761 // Check for xcache_isset and emit warning if exists
762 $this->checkXCache(array(
763 ts("PHP Configuration"),
764 ts("XCache compatibility"),
765 ts("XCache is installed and there are known compatibility issues between XCache and CiviCRM. Consider using an alternative PHP caching mechanism or disable PHP caching altogether."),
768 // Check memory allocation
769 $this->requireMemory(32 * 1024 * 1024,
772 ts("PHP Configuration"),
773 ts("Memory allocated (PHP config option 'memory_limit')"),
774 ts("CiviCRM needs a minimum of %1 MB allocated to PHP, but recommends %2 MB.", array(1 => 32, 2 => 64)),
775 ini_get("memory_limit"),
779 return $this->errors
;
784 * @param $recommended
785 * @param $testDetails
787 public function requireMemory($min, $recommended, $testDetails) {
788 $this->testing($testDetails);
789 $mem = $this->getPHPMemory();
791 if ($mem < $min && $mem > 0) {
792 $testDetails[2] .= " " . ts("You only have %1 allocated", array(1 => ini_get("memory_limit")));
793 $this->error($testDetails);
795 elseif ($mem < $recommended && $mem > 0) {
796 $testDetails[2] .= " " . ts("You only have %1 allocated", array(1 => ini_get("memory_limit")));
797 $this->warning($testDetails);
800 $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));
801 $this->warning($testDetails);
808 public function getPHPMemory() {
809 $memString = ini_get("memory_limit");
811 switch (strtolower(substr($memString, -1))) {
813 return round(substr($memString, 0, -1) * 1024);
816 return round(substr($memString, 0, -1) * 1024 * 1024);
819 return round(substr($memString, 0, -1) * 1024 * 1024 * 1024);
822 return round($memString);
826 public function listErrors() {
828 echo "<p>" . ts("The following problems are preventing me from installing CiviCRM:") . "</p>";
829 foreach ($this->errors
as $error) {
830 echo "<li>" . htmlentities($error) . "</li>";
836 * @param null $section
838 public function showTable($section = NULL) {
840 $tests = $this->tests
[$section];
841 echo "<table class=\"testResults\" width=\"100%\">";
842 foreach ($tests as $test => $result) {
843 echo "<tr class=\"$result[0]\"><td>$test</td><td>" . nl2br(htmlentities($result[1])) . "</td></tr>";
848 foreach ($this->tests
as $section => $tests) {
849 echo "<h3>$section</h3>";
850 echo "<table class=\"testResults\" width=\"100%\">";
852 foreach ($tests as $test => $result) {
853 echo "<tr class=\"$result[0]\"><td>$test</td><td>" . nl2br(htmlentities($result[1])) . "</td></tr>";
861 * @param string $funcName
862 * @param $testDetails
866 public function requireFunction($funcName, $testDetails) {
867 $this->testing($testDetails);
869 if (!function_exists($funcName)) {
870 $this->error($testDetails);
879 * @param $testDetails
881 public function checkXCache($testDetails) {
882 if (function_exists('xcache_isset') &&
883 ini_get('xcache.size') > 0
885 $this->testing($testDetails);
886 $this->warning($testDetails);
891 * @param array $testDetails
894 public function requirePHPVersion($testDetails) {
896 $this->testing($testDetails);
898 $phpVersion = phpversion();
899 $aboveMinVersion = version_compare($phpVersion, CRM_Upgrade_Incremental_General
::MIN_INSTALL_PHP_VER
) >= 0;
901 if ($aboveMinVersion) {
902 if (version_compare($phpVersion, CRM_Upgrade_Incremental_General
::MIN_RECOMMENDED_PHP_VER
) < 0) {
903 $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(
905 2 => CRM_Upgrade_Incremental_General
::MIN_RECOMMENDED_PHP_VER
,
906 3 => CRM_Upgrade_Incremental_General
::RECOMMENDED_PHP_VER
,
908 $this->warning($testDetails);
913 if (empty($testDetails[2])) {
914 $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));
917 $this->error($testDetails);
921 * @param string $filename
922 * @param $testDetails
923 * @param bool $absolute
925 public function requireFile($filename, $testDetails, $absolute = FALSE) {
926 $this->testing($testDetails);
928 $filename = $this->getBaseDir() . $filename;
930 if (!file_exists($filename)) {
931 $testDetails[2] .= " (" . ts("file '%1' not found", array(1 => $filename)) . ')';
932 $this->error($testDetails);
937 * @param $testDetails
939 public function requireNoPathSeparator($testDetails) {
940 $this->testing($testDetails);
941 if (substr_count($this->getBaseDir(), PATH_SEPARATOR
)) {
942 $this->error($testDetails);
947 * @param string $filename
948 * @param $testDetails
950 public function requireNoFile($filename, $testDetails) {
951 $this->testing($testDetails);
952 $filename = $this->getBaseDir() . $filename;
953 if (file_exists($filename)) {
954 $testDetails[2] .= " (" . ts("file '%1' found", array(1 => $filename)) . ")";
955 $this->error($testDetails);
960 * @param string $filename
961 * @param $testDetails
963 public function moveFileOutOfTheWay($filename, $testDetails) {
964 $this->testing($testDetails);
965 $filename = $this->getBaseDir() . $filename;
966 if (file_exists($filename)) {
967 if (file_exists("$filename.bak")) {
970 rename($filename, "$filename.bak");
975 * @param string $filename
976 * @param $testDetails
977 * @param bool $absolute
979 public function requireWriteable($filename, $testDetails, $absolute = FALSE) {
980 $this->testing($testDetails);
982 $filename = $this->getBaseDir() . $filename;
985 if (!is_writable($filename)) {
987 if (function_exists('posix_getpwuid')) {
988 $user = posix_getpwuid(posix_geteuid());
989 $name = '- ' . $user['name'] . ' -';
992 if (!isset($testDetails[2])) {
993 $testDetails[2] = NULL;
995 $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";
996 $this->error($testDetails);
1001 * @param string $moduleName
1002 * @param $testDetails
1004 public function requireApacheModule($moduleName, $testDetails) {
1005 $this->testing($testDetails);
1006 if (!in_array($moduleName, apache_get_modules())) {
1007 $this->error($testDetails);
1013 * @param string $username
1015 * @param $testDetails
1017 public function requireMysqlConnection($server, $username, $password, $testDetails) {
1018 $this->testing($testDetails);
1019 $this->conn
= $this->connect($server, $username, $password);
1025 $testDetails[2] .= ": " . mysqli_connect_error();
1026 $this->error($testDetails);
1032 * @param $testDetails
1034 public function requireMySQLServer($server, $testDetails) {
1035 $this->testing($testDetails);
1036 $conn = $this->connect($server, NULL, NULL);
1038 if ($conn ||
mysqli_connect_errno() < 2000) {
1042 $testDetails[2] .= ": " . mysqli_connect_error();
1043 $this->error($testDetails);
1049 * @param $testDetails
1051 public function requireMySQLVersion($version, $testDetails) {
1052 $this->testing($testDetails);
1054 if (!mysqli_get_server_info($this->conn
)) {
1055 $testDetails[2] = ts('Cannot determine the version of MySQL installed. Please ensure at least version %1 is installed.', array(1 => $version));
1056 $this->warning($testDetails);
1059 list($majorRequested, $minorRequested) = explode('.', $version);
1060 list($majorHas, $minorHas) = explode('.', mysqli_get_server_info($this->conn
));
1062 if (($majorHas > $majorRequested) ||
($majorHas == $majorRequested && $minorHas >= $minorRequested)) {
1066 $testDetails[2] .= "{$majorHas}.{$minorHas}.";
1067 $this->error($testDetails);
1074 * @param string $username
1077 * @param $testDetails
1079 public function requireMySQLInnoDB($server, $username, $password, $database, $testDetails) {
1080 $this->testing($testDetails);
1081 $conn = $this->connect($server, $username, $password);
1083 $testDetails[2] .= ' ' . ts("Could not determine if MySQL has InnoDB support. Assuming no.");
1084 $this->error($testDetails);
1088 $innodb_support = FALSE;
1089 $result = mysqli_query($conn, "SHOW ENGINES");
1090 while ($values = mysqli_fetch_array($result)) {
1091 if ($values['Engine'] == 'InnoDB') {
1092 if (strtolower($values['Support']) == 'yes' ||
1093 strtolower($values['Support']) == 'default'
1095 $innodb_support = TRUE;
1099 if ($innodb_support) {
1100 $testDetails[3] = ts('MySQL server does have InnoDB support');
1103 $testDetails[2] .= ' ' . ts('Could not determine if MySQL has InnoDB support. Assuming no');
1109 * @param string $username
1112 * @param $testDetails
1114 public function requireMySQLTempTables($server, $username, $password, $database, $testDetails) {
1115 $this->testing($testDetails);
1116 $conn = $this->connect($server, $username, $password);
1118 $testDetails[2] = ts('Could not login to the database.');
1119 $this->error($testDetails);
1123 if (!@mysqli_select_db
($conn, $database)) {
1124 $testDetails[2] = ts('Could not select the database.');
1125 $this->error($testDetails);
1129 $result = mysqli_query($conn, 'CREATE TEMPORARY TABLE civicrm_install_temp_table_test (test text)');
1131 $testDetails[2] = ts('Could not create a temp table.');
1132 $this->error($testDetails);
1134 $result = mysqli_query($conn, 'DROP TEMPORARY TABLE civicrm_install_temp_table_test');
1139 * @param string $username
1142 * @param $testDetails
1144 public function requireMySQLTrigger($server, $username, $password, $database, $testDetails) {
1145 $this->testing($testDetails);
1146 $conn = $this->connect($server, $username, $password);
1148 $testDetails[2] = ts('Could not login to the database.');
1149 $this->error($testDetails);
1153 if (!@mysqli_select_db
($conn, $database)) {
1154 $testDetails[2] = ts('Could not select the database.');
1155 $this->error($testDetails);
1159 $result = mysqli_query($conn, 'CREATE TABLE civicrm_install_temp_table_test (test text)');
1161 $testDetails[2] = ts('Could not create a table in the database.');
1162 $this->error($testDetails);
1165 $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');
1167 mysqli_query($conn, 'DROP TABLE civicrm_install_temp_table_test');
1168 $testDetails[2] = ts('Could not create a database trigger.');
1169 $this->error($testDetails);
1172 mysqli_query($conn, 'DROP TRIGGER civicrm_install_temp_table_test_trigger');
1173 mysqli_query($conn, 'DROP TABLE civicrm_install_temp_table_test');
1178 * @param string $username
1181 * @param $testDetails
1183 public function requireMySQLLockTables($server, $username, $password, $database, $testDetails) {
1184 $this->testing($testDetails);
1185 $conn = $this->connect($server, $username, $password);
1187 $testDetails[2] = ts('Could not connect to the database server.');
1188 $this->error($testDetails);
1192 if (!@mysqli_select_db
($conn, $database)) {
1193 $testDetails[2] = ts('Could not select the database.');
1194 $this->error($testDetails);
1198 $result = mysqli_query($conn, 'CREATE TEMPORARY TABLE civicrm_install_temp_table_test (test text)');
1200 $testDetails[2] = ts('Could not create a table in the database.');
1201 $this->error($testDetails);
1205 $result = mysqli_query($conn, 'LOCK TABLES civicrm_install_temp_table_test WRITE');
1207 $testDetails[2] = ts('Could not obtain a write lock for the database table.');
1208 $this->error($testDetails);
1209 $result = mysqli_query($conn, 'DROP TEMPORARY TABLE civicrm_install_temp_table_test');
1213 $result = mysqli_query($conn, 'UNLOCK TABLES');
1215 $testDetails[2] = ts('Could not release the lock for the database table.');
1216 $this->error($testDetails);
1217 $result = mysqli_query($conn, 'DROP TEMPORARY TABLE civicrm_install_temp_table_test');
1221 $result = mysqli_query($conn, 'DROP TEMPORARY TABLE civicrm_install_temp_table_test');
1226 * @param string $username
1228 * @param $testDetails
1230 public function requireMySQLAutoIncrementIncrementOne($server, $username, $password, $testDetails) {
1231 $this->testing($testDetails);
1232 $conn = $this->connect($server, $username, $password);
1234 $testDetails[2] = ts('Could not connect to the database server.');
1235 $this->error($testDetails);
1239 $result = mysqli_query($conn, "SHOW variables like 'auto_increment_increment'");
1241 $testDetails[2] = ts('Could not query database server variables.');
1242 $this->error($testDetails);
1246 $values = mysqli_fetch_row($result);
1247 if ($values[1] == 1) {
1248 $testDetails[3] = ts('MySQL server auto_increment_increment is 1');
1251 $this->error($testDetails);
1258 * @param string $username
1261 * @param $minValueKB
1262 * @param $testDetails
1264 public function requireMySQLThreadStack($server, $username, $password, $database, $minValueKB, $testDetails) {
1265 $this->testing($testDetails);
1266 $conn = $this->connect($server, $username, $password);
1268 $testDetails[2] = ts('Could not connect to the database server.');
1269 $this->error($testDetails);
1273 if (!@mysqli_select_db
($conn, $database)) {
1274 $testDetails[2] = ts('Could not select the database.');
1275 $this->error($testDetails);
1280 $result = mysqli_query($conn, "SHOW VARIABLES LIKE 'thread_stack'");
1282 $testDetails[2] = ts('Could not get information about the thread_stack of the database.');
1283 $this->error($testDetails);
1286 $values = mysqli_fetch_row($result);
1287 if ($values[1] < (1024 * $minValueKB)) {
1288 $testDetails[2] = ts('MySQL "thread_stack" is %1 kb', array(1 => ($values[1] / 1024)));
1289 $this->error($testDetails);
1299 * @param $testDetails
1301 public function requireNoExistingData(
1308 $this->testing($testDetails);
1309 $conn = $this->connect($server, $username, $password);
1311 @mysqli_select_db
($conn, $database);
1312 $contactRecords = mysqli_query($conn, "SELECT count(*) as contactscount FROM civicrm_contact");
1313 if ($contactRecords) {
1314 $contactRecords = mysqli_fetch_object($contactRecords);
1315 if ($contactRecords->contactscount
> 0) {
1316 $this->error($testDetails);
1321 $testDetails[3] = ts('CiviCRM data from previous installation does not exist in %1.', array(1 => $database));
1322 $this->testing($testDetails);
1327 * @param string $username
1330 * @param $testDetails
1331 * @param bool $onlyRequire
1333 public function requireDatabaseOrCreatePermissions(
1339 $onlyRequire = FALSE
1341 $this->testing($testDetails);
1342 $conn = $this->connect($server, $username, $password);
1345 if (@mysqli_select_db
($conn, $database)) {
1346 $okay = "Database '$database' exists";
1348 elseif ($onlyRequire) {
1349 $testDetails[2] = ts("The database: '%1' does not exist.", array(1 => $database));
1350 $this->error($testDetails);
1354 $query = sprintf("CREATE DATABASE %s", mysqli_real_escape_string($conn, $database));
1355 if (@mysqli_query
($conn, $query)) {
1356 $okay = ts("Able to create a new database.");
1359 $testDetails[2] .= " (" . ts("user '%1' doesn't have CREATE DATABASE permissions.", array(1 => $username)) . ")";
1360 $this->error($testDetails);
1366 $testDetails[3] = $okay;
1367 $this->testing($testDetails);
1373 * @param $errorMessage
1375 public function requireServerVariables($varNames, $errorMessage) {
1376 //$this->testing($testDetails);
1377 foreach ($varNames as $varName) {
1378 if (!$_SERVER[$varName]) {
1379 $missing[] = '$_SERVER[' . $varName . ']';
1382 if (!isset($missing)) {
1386 $testDetails[2] = " (" . ts('the following PHP variables are missing: %1', array(1 => implode(", ", $missing))) . ")";
1387 $this->error($testDetails);
1393 * @param string $username
1396 * @param $testDetails
1398 public function requireMysqlUtf8mb4($server, $username, $password, $database, $testDetails) {
1399 $this->testing($testDetails);
1400 $conn = $this->connect($server, $username, $password);
1402 $testDetails[2] = ts('Could not connect to the database server.');
1403 $this->error($testDetails);
1407 if (!@mysqli_select_db
($conn, $database)) {
1408 $testDetails[2] = ts('Could not select the database.');
1409 $this->error($testDetails);
1413 $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');
1415 $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');
1416 $this->warning($testDetails);
1419 $result = mysqli_query($conn, 'DROP TABLE civicrm_utf8mb4_test');
1421 // Ensure that the MySQL driver supports utf8mb4 encoding.
1422 $version = mysqli_get_client_info($conn);
1423 if (strpos($version, 'mysqlnd') !== FALSE) {
1424 // The mysqlnd driver supports utf8mb4 starting at version 5.0.9.
1425 $version = preg_replace('/^\D+([\d.]+).*/', '$1', $version);
1426 if (version_compare($version, '5.0.9', '<')) {
1427 $testDetails[2] = 'It is recommended, though not yet required, to upgrade your PHP MySQL driver (mysqlnd) to >= 5.0.9 for utf8mb4 support.';
1428 $this->warning($testDetails);
1433 // The libmysqlclient driver supports utf8mb4 starting at version 5.5.3.
1434 if (version_compare($version, '5.5.3', '<')) {
1435 $testDetails[2] = 'It is recommended, though not yet required, to upgrade your PHP MySQL driver (libmysqlclient) to >= 5.5.3 for utf8mb4 support.';
1436 $this->warning($testDetails);
1443 * @param $testDetails
1447 public function isRunningApache($testDetails) {
1448 $this->testing($testDetails);
1449 if (function_exists('apache_get_modules') ||
stristr($_SERVER['SERVER_SIGNATURE'], 'Apache')) {
1453 $this->warning($testDetails);
1460 public function getBaseDir() {
1461 return dirname($_SERVER['SCRIPT_FILENAME']) . CIVICRM_DIRECTORY_SEPARATOR
;
1465 * @param $testDetails
1467 public function testing($testDetails) {
1468 if (!$testDetails) {
1472 $section = $testDetails[0];
1473 $test = $testDetails[1];
1475 $message = ts("OK");
1476 if (isset($testDetails[3])) {
1477 $message .= " ($testDetails[3])";
1480 $this->tests
[$section][$test] = array("good", $message);
1484 * @param $testDetails
1486 public function error($testDetails) {
1487 $section = $testDetails[0];
1488 $test = $testDetails[1];
1490 $this->tests
[$section][$test] = array("error", $testDetails[2]);
1491 $this->errors
[] = $testDetails;
1495 * @param $testDetails
1497 public function warning($testDetails) {
1498 $section = $testDetails[0];
1499 $test = $testDetails[1];
1501 $this->tests
[$section][$test] = array("warning", $testDetails[2]);
1502 $this->warnings
[] = $testDetails;
1508 public function hasErrors() {
1509 return !empty($this->errors
);
1515 public function hasWarnings() {
1516 return !empty($this->warnings
);
1524 class Installer
extends InstallRequirements
{
1532 public function createDatabaseIfNotExists($server, $username, $password, $database) {
1533 $conn = $this->connect($server, $username, $password);
1535 if (@mysqli_select_db
($conn, $database)) {
1536 // skip if database already present
1539 $query = sprintf("CREATE DATABASE %s", mysqli_real_escape_string($conn, $database));
1540 if (@mysqli_query
($conn, $query)) {
1543 $errorTitle = ts("Oops! Could not create database %1", array(1 => $database));
1544 $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.");
1545 errorDisplayPage($errorTitle, $errorMsg);
1554 public function install($config) {
1555 global $installDirPath;
1557 // create database if does not exists
1558 $this->createDatabaseIfNotExists($config['mysql']['server'],
1559 $config['mysql']['username'],
1560 $config['mysql']['password'],
1561 $config['mysql']['database']
1564 global $installDirPath;
1567 require_once $installDirPath . 'civicrm.php';
1568 civicrm_main($config);
1570 if (!$this->errors
) {
1571 global $installType, $installURLPath;
1573 $registerSiteURL = "https://civicrm.org/register-site";
1574 $commonOutputMessage
1575 = "<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>"
1576 . "<li>" . ts("We have integrated KCFinder with CKEditor and TinyMCE. This allows a user to upload images. All uploaded images are public.") . "</li>";
1581 $installType == 'drupal' &&
1582 version_compare(VERSION
, '7.0-rc1') >= 0
1588 $output .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
1589 $output .= '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">';
1590 $output .= '<head>';
1591 $output .= '<title>' . ts('CiviCRM Installed') . '</title>';
1592 $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
1593 $output .= '<link rel="stylesheet" type="text/css" href="template.css" />';
1594 $output .= '</head>';
1595 $output .= '<body>';
1596 $output .= '<div style="padding: 1em;"><p class="good">' . ts('CiviCRM has been successfully installed') . '</p>';
1599 $drupalURL = civicrm_cms_base();
1600 $drupalPermissionsURL = "{$drupalURL}index.php?q=admin/people/permissions";
1601 $drupalURL .= "index.php?q=civicrm/admin/configtask&reset=1";
1603 $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>";
1604 $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>";
1605 $output .= $commonOutputMessage;
1607 // automatically enable CiviCRM module once it is installed successfully.
1608 // so we need to Bootstrap Drupal, so that we can call drupal hooks.
1609 global $cmsPath, $crmPath;
1611 // relative / abosolute paths are not working for drupal, hence using chdir()
1614 // Force the re-initialisation of the config singleton on the next call
1615 // since so far, we had used the Config object without loading the DB.
1616 $c = CRM_Core_Config
::singleton(FALSE);
1619 include_once "./includes/bootstrap.inc";
1620 include_once "./includes/unicode.inc";
1622 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL
);
1624 // prevent session information from being saved.
1625 drupal_save_session(FALSE);
1627 // Force the current user to anonymous.
1628 $original_user = $GLOBALS['user'];
1629 $GLOBALS['user'] = drupal_anonymous_user();
1631 // explicitly setting error reporting, since we cannot handle drupal related notices
1634 // rebuild modules, so that civicrm is added
1635 system_rebuild_module_data();
1637 // now enable civicrm module.
1638 module_enable(array('civicrm', 'civicrmtheme'));
1640 // SystemInstallEvent will be called from here with the first call of CRM_Core_Config,
1641 // which calls Core_BAO_ConfigSetting::applyLocale(), who will default to calling
1642 // Civi::settings()->get('lcMessages');
1643 // Therefore, we need to pass the seedLanguage before that.
1644 global $civicrm_setting;
1645 $civicrm_setting['domain']['lcMessages'] = $config['seedLanguage'];
1647 // clear block, page, theme, and hook caches
1648 drupal_flush_all_caches();
1650 //add basic drupal permissions
1651 civicrm_install_set_drupal_perms();
1653 // restore the user.
1654 $GLOBALS['user'] = $original_user;
1655 drupal_save_session(TRUE);
1658 $output .= '</div>';
1659 $output .= '</body>';
1660 $output .= '</html>';
1664 $installType == 'backdrop'
1670 $output .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
1671 $output .= '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">';
1672 $output .= '<head>';
1673 $output .= '<title>' . ts('CiviCRM Installed') . '</title>';
1674 $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
1675 $output .= '<link rel="stylesheet" type="text/css" href="template.css" />';
1676 $output .= '</head>';
1677 $output .= '<body>';
1678 $output .= '<div style="padding: 1em;"><p class="good">' . ts('CiviCRM has been successfully installed') . '</p>';
1681 $backdropURL = civicrm_cms_base();
1682 $backdropPermissionsURL = "{$backdropURL}index.php?q=admin/config/people/permissions";
1683 $backdropURL .= "index.php?q=civicrm/admin/configtask&reset=1";
1685 $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>";
1686 $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>";
1687 $output .= $commonOutputMessage;
1689 // automatically enable CiviCRM module once it is installed successfully.
1690 // so we need to Bootstrap Drupal, so that we can call drupal hooks.
1691 global $cmsPath, $crmPath;
1693 // relative / abosolute paths are not working for drupal, hence using chdir()
1696 // Force the re-initialisation of the config singleton on the next call
1697 // since so far, we had used the Config object without loading the DB.
1698 $c = CRM_Core_Config
::singleton(FALSE);
1701 include_once "./core/includes/bootstrap.inc";
1702 include_once "./core/includes/unicode.inc";
1703 include_once "./core/includes/config.inc";
1705 backdrop_bootstrap(BACKDROP_BOOTSTRAP_FULL
);
1707 // prevent session information from being saved.
1708 backdrop_save_session(FALSE);
1710 // Force the current user to anonymous.
1711 $original_user = $GLOBALS['user'];
1712 $GLOBALS['user'] = backdrop_anonymous_user();
1714 // explicitly setting error reporting, since we cannot handle drupal related notices
1717 // rebuild modules, so that civicrm is added
1718 system_rebuild_module_data();
1720 // now enable civicrm module.
1721 module_enable(array('civicrm', 'civicrmtheme'));
1723 // clear block, page, theme, and hook caches
1724 backdrop_flush_all_caches();
1726 //add basic backdrop permissions
1727 civicrm_install_set_backdrop_perms();
1729 // restore the user.
1730 $GLOBALS['user'] = $original_user;
1731 backdrop_save_session(TRUE);
1733 //change the default language to one chosen
1734 if (isset($config['seedLanguage']) && $config['seedLanguage'] != 'en_US') {
1735 civicrm_api3('Setting', 'create', array(
1736 'domain_id' => 'current_domain',
1737 'lcMessages' => $config['seedLanguage'],
1742 $output .= '</div>';
1743 $output .= '</body>';
1744 $output .= '</html>';
1747 elseif ($installType == 'drupal' && version_compare(VERSION
, '6.0') >= 0) {
1751 $output .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
1752 $output .= '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">';
1753 $output .= '<head>';
1754 $output .= '<title>' . ts('CiviCRM Installed') . '</title>';
1755 $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
1756 $output .= '<link rel="stylesheet" type="text/css" href="template.css" />';
1757 $output .= '</head>';
1758 $output .= '<body>';
1759 $output .= '<div style="padding: 1em;"><p class="good">' . ts("CiviCRM has been successfully installed") . '</p>';
1762 $drupalURL = civicrm_cms_base();
1763 $drupalPermissionsURL = "{$drupalURL}index.php?q=admin/user/permissions";
1764 $drupalURL .= "index.php?q=civicrm/admin/configtask&reset=1";
1766 $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>";
1767 $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>";
1768 $output .= $commonOutputMessage;
1770 // explicitly setting error reporting, since we cannot handle drupal related notices
1773 // automatically enable CiviCRM module once it is installed successfully.
1774 // so we need to Bootstrap Drupal, so that we can call drupal hooks.
1775 global $cmsPath, $crmPath;
1777 // relative / abosolute paths are not working for drupal, hence using chdir()
1780 // Force the re-initialisation of the config singleton on the next call
1781 // since so far, we had used the Config object without loading the DB.
1782 $c = CRM_Core_Config
::singleton(FALSE);
1785 include_once "./includes/bootstrap.inc";
1786 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL
);
1788 // rebuild modules, so that civicrm is added
1789 module_rebuild_cache();
1791 // now enable civicrm module.
1792 module_enable(array('civicrm'));
1794 // clear block, page, theme, and hook caches
1795 drupal_flush_all_caches();
1797 //add basic drupal permissions
1798 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)');
1802 elseif ($installType == 'wordpress') {
1803 echo '<h1>' . ts('CiviCRM Installed') . '</h1>';
1804 echo '<div style="padding: 1em;"><p style="background-color: #0C0; border: 1px #070 solid; color: white;">' . ts("CiviCRM has been successfully installed") . '</p>';
1807 $cmsURL = civicrm_cms_base();
1808 $cmsURL .= "wp-admin/admin.php?page=CiviCRM&q=civicrm/admin/configtask&reset=1";
1809 $wpPermissionsURL = "wp-admin/admin.php?page=CiviCRM&q=civicrm/admin/access/wp-permissions&reset=1";
1811 $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>";
1812 $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>";
1813 $output .= $commonOutputMessage;
1816 $output .= '</div>';
1819 $c = CRM_Core_Config
::singleton(FALSE);
1821 $wpInstallRedirect = admin_url('admin.php?page=CiviCRM&q=civicrm&reset=1');
1823 window.location = '$wpInstallRedirect';
1828 return $this->errors
;
1833 function civicrm_install_set_drupal_perms() {
1834 if (!function_exists('db_select')) {
1835 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)');
1839 'access all custom data',
1840 'access uploaded files',
1841 'make online contributions',
1845 'register for events',
1847 'view event participants',
1848 'access CiviMail subscribe/unsubscribe pages',
1851 // Adding a permission that has not yet been assigned to a module by
1852 // a hook_permission implementation results in a database error.
1854 $allPerms = array_keys(module_invoke_all('permission'));
1855 foreach (array_diff($perms, $allPerms) as $perm) {
1857 'Cannot grant the %perm permission because it does not yet exist.',
1858 array('%perm' => $perm),
1862 $perms = array_intersect($perms, $allPerms);
1863 user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID
, $perms);
1864 user_role_grant_permissions(DRUPAL_ANONYMOUS_RID
, $perms);
1868 function civicrm_install_set_backdrop_perms() {
1870 'access all custom data',
1871 'access uploaded files',
1872 'make online contributions',
1876 'register for events',
1878 'view event participants',
1879 'access CiviMail subscribe/unsubscribe pages',
1882 // Adding a permission that has not yet been assigned to a module by
1883 // a hook_permission implementation results in a database error.
1885 $allPerms = array_keys(module_invoke_all('permission'));
1886 foreach (array_diff($perms, $allPerms) as $perm) {
1888 'Cannot grant the %perm permission because it does not yet exist.',
1889 array('%perm' => $perm),
1893 $perms = array_intersect($perms, $allPerms);
1894 user_role_grant_permissions(BACKDROP_AUTHENTICATED_ROLE
, $perms);
1895 user_role_grant_permissions(BACKDROP_ANONYMOUS_ROLE
, $perms);
1904 function getSiteDir($cmsPath, $str) {
1905 static $siteDir = '';
1911 $sites = CIVICRM_DIRECTORY_SEPARATOR
. 'sites' . CIVICRM_DIRECTORY_SEPARATOR
;
1912 $modules = CIVICRM_DIRECTORY_SEPARATOR
. 'modules' . CIVICRM_DIRECTORY_SEPARATOR
;
1913 preg_match("/" . preg_quote($sites, CIVICRM_DIRECTORY_SEPARATOR
) .
1914 "([\-a-zA-Z0-9_.]+)" .
1915 preg_quote($modules, CIVICRM_DIRECTORY_SEPARATOR
) . "/",
1916 $_SERVER['SCRIPT_FILENAME'], $matches
1918 $siteDir = isset($matches[1]) ?
$matches[1] : 'default';
1920 if (strtolower($siteDir) == 'all') {
1921 // For this case - use drupal's way of finding out multi-site directory
1922 $uri = explode(CIVICRM_DIRECTORY_SEPARATOR
, $_SERVER['SCRIPT_FILENAME']);
1923 $server = explode('.', implode('.', array_reverse(explode(':', rtrim($_SERVER['HTTP_HOST'], '.')))));
1924 for ($i = count($uri) - 1; $i > 0; $i--) {
1925 for ($j = count($server); $j > 0; $j--) {
1926 $dir = implode('.', array_slice($server, -$j)) . implode('.', array_slice($uri, 0, $i));
1927 if (file_exists($cmsPath . CIVICRM_DIRECTORY_SEPARATOR
.
1928 'sites' . CIVICRM_DIRECTORY_SEPARATOR
. $dir
1935 $siteDir = 'default';
1942 * @param $errorTitle
1946 function errorDisplayPage($errorTitle, $errorMsg, $showRefer = TRUE) {
1948 // Add a link to the documentation
1950 if (is_callable(array('CRM_Utils_System', 'docURL2'))) {
1951 $docLink = CRM_Utils_System
::docURL2('Installation and Upgrades', FALSE, 'Installation Guide', NULL, NULL, "wiki");
1957 if (function_exists('ts')) {
1958 $errorMsg .= '<p>' . ts("Refer to the online documentation for more information: ") . $docLink . '</p>';
1961 $errorMsg .= '<p>' . 'Refer to the online documentation for more information: ' . $docLink . '</p>';
1965 include 'error.html';