3 namespace Civi\Install
;
7 * @package Civi\Install
12 * Requirement severity -- Requirement successfully met.
14 const REQUIREMENT_OK
= 0;
17 * Requirement severity -- Warning condition; proceed but flag warning.
19 const REQUIREMENT_WARNING
= 1;
22 * Requirement severity -- Error condition; abort installation.
24 const REQUIREMENT_ERROR
= 2;
29 protected $system_checks = [
31 'checkServerVariables',
32 'checkMysqlConnectExists',
33 'checkJsonEncodeExists',
34 'checkMultibyteExists',
37 protected $database_checks = [
38 'checkMysqlConnection',
41 'checkMysqlTempTables',
42 'checkMySQLAutoIncrementIncrementOne',
44 'checkMysqlThreadStack',
45 'checkMysqlLockTables',
50 * Run all requirements tests.
52 * @param array $config
53 * An array with two keys:
58 * An array of check summaries. Each array contains the keys 'title', 'severity', and 'details'.
60 public function checkAll(array $config) {
61 if (!class_exists('\CRM_Utils_SQL_TempTable')) {
62 require_once dirname(__FILE__
) . '/../../CRM/Utils/SQL/TempTable.php';
64 return array_merge($this->checkSystem($config['file_paths']), $this->checkDatabase($config['db_config']));
68 * Check system requirements are met, such as sufficient memory,
69 * necessary file paths are writable and required php extensions
72 * @param array $file_paths
73 * An array of file paths that will be checked to confirm they
78 public function checkSystem(array $file_paths) {
81 $errors[] = $this->checkFilepathIsWritable($file_paths);
82 foreach ($this->system_checks
as $check) {
83 $errors[] = $this->$check();
90 * Check database connection, database version and other
91 * database requirements are met.
93 * @param array $db_config
95 * - host (with optional port specified eg. localhost:12345)
96 * - database (name of database to select)
102 public function checkDatabase(array $db_config) {
105 foreach ($this->database_checks
as $check) {
106 $errors[] = $this->$check($db_config);
113 * Generates a mysql connection
115 * @param $db_config array
116 * @return object mysqli connection
118 protected function connect($db_config) {
120 if (!empty($db_config['host'])) {
121 $host = $db_config['host'];
123 elseif (!empty($db_config['server'])) {
124 $host = $db_config['server'];
126 $conn = @mysqli_connect
($host, $db_config['username'], $db_config['password'], $db_config['database'], !empty($db_config['port']) ?
$db_config['port'] : NULL);
131 * Check configured php Memory.
134 public function checkMemory() {
135 $min = 1024 * 1024 * 32;
136 $recommended = 1024 * 1024 * 64;
138 $mem = $this->getPHPMemory();
139 $mem_string = ini_get('memory_limit');
142 'title' => 'CiviCRM memory check',
143 'severity' => $this::REQUIREMENT_OK
,
144 'details' => "You have $mem_string allocated (minimum 32Mb, recommended 64Mb)",
147 if ($mem < $min && $mem > 0) {
148 $results['severity'] = $this::REQUIREMENT_ERROR
;
150 elseif ($mem < $recommended && $mem != 0) {
151 $results['severity'] = $this::REQUIREMENT_WARNING
;
154 $results['details'] = "Cannot determine PHP memory allocation. Install only if you're sure you've allocated at least 32 MB.";
155 $results['severity'] = $this::REQUIREMENT_WARNING
;
162 * Get Configured PHP memory.
165 protected function getPHPMemory() {
166 $memString = ini_get("memory_limit");
168 switch (strtolower(substr($memString, -1))) {
170 return round(substr($memString, 0, -1) * 1024);
173 return round(substr($memString, 0, -1) * 1024 * 1024);
176 return round(substr($memString, 0, -1) * 1024 * 1024 * 1024);
179 return round($memString);
186 public function checkServerVariables() {
188 'title' => 'CiviCRM PHP server variables',
189 'severity' => $this::REQUIREMENT_OK
,
190 'details' => 'The required $_SERVER variables are set',
193 $required_variables = ['SCRIPT_NAME', 'HTTP_HOST', 'SCRIPT_FILENAME'];
196 foreach ($required_variables as $required_variable) {
197 if (empty($_SERVER[$required_variable])) {
198 $missing[] = '$_SERVER[' . $required_variable . ']';
203 $results['severity'] = $this::REQUIREMENT_ERROR
;
204 $results['details'] = 'The following PHP variables are not set: ' . implode(', ', $missing);
213 public function checkJsonEncodeExists() {
215 'title' => 'CiviCRM JSON encoding support',
216 'severity' => $this::REQUIREMENT_OK
,
217 'details' => 'Function json_encode() found',
219 if (!function_exists('json_encode')) {
220 $results['severity'] = $this::REQUIREMENT_ERROR
;
221 $results['details'] = 'Function json_encode() does not exist';
228 * CHeck that PHP Multibyte functions are enabled.
231 public function checkMultibyteExists() {
233 'title' => 'CiviCRM MultiByte encoding support',
234 'severity' => $this::REQUIREMENT_OK
,
235 'details' => 'PHP Multibyte etension found',
237 if (!function_exists('mb_substr')) {
238 $results['severity'] = $this::REQUIREMENT_ERROR
;
239 $results['details'] = 'PHP Multibyte extension has not been installed and enabled';
248 public function checkMysqlConnectExists() {
250 'title' => 'CiviCRM MySQL check',
251 'severity' => $this::REQUIREMENT_OK
,
252 'details' => 'Function mysqli_connect() found',
254 if (!function_exists('mysqli_connect')) {
255 $results['severity'] = $this::REQUIREMENT_ERROR
;
256 $results['details'] = 'Function mysqli_connect() does not exist';
263 * @param array $db_config
267 public function checkMysqlConnection(array $db_config) {
269 'title' => 'CiviCRM MySQL connection',
270 'severity' => $this::REQUIREMENT_OK
,
271 'details' => "Connected",
274 $conn = $this->connect($db_config);
277 $results['details'] = mysqli_connect_error();
278 $results['severity'] = $this::REQUIREMENT_ERROR
;
282 if (!@mysqli_select_db
($conn, $db_config['database'])) {
283 $results['details'] = mysqli_error($conn);
284 $results['severity'] = $this::REQUIREMENT_ERROR
;
292 * @param array $db_config
296 public function checkMysqlVersion(array $db_config) {
297 $min = \CRM_Upgrade_Incremental_General
::MIN_INSTALL_MYSQL_VER
;
299 'title' => 'CiviCRM MySQL Version',
300 'severity' => $this::REQUIREMENT_OK
,
303 $conn = $this->connect($db_config);
304 if (!$conn ||
!($info = mysqli_get_server_info($conn))) {
305 $results['severity'] = $this::REQUIREMENT_WARNING
;
306 $results['details'] = "Cannot determine the version of MySQL installed. Please ensure at least version {$min} is installed.";
310 if (version_compare($info, $min) == -1) {
311 $results['severity'] = $this::REQUIREMENT_ERROR
;
312 $results['details'] = "MySQL version is {$info}; minimum required is {$min}";
316 $results['details'] = "MySQL version is {$info}";
321 * @param array $db_config
325 public function checkMysqlInnodb(array $db_config) {
327 'title' => 'CiviCRM InnoDB support',
328 'severity' => $this::REQUIREMENT_ERROR
,
329 'details' => 'Could not determine if MySQL has InnoDB support. Assuming none.',
332 $conn = $this->connect($db_config);
337 $innodb_support = FALSE;
338 $result = mysqli_query($conn, "SHOW ENGINES");
339 while ($values = mysqli_fetch_array($result)) {
340 if ($values['Engine'] == 'InnoDB') {
341 if (strtolower($values['Support']) == 'yes' ||
strtolower($values['Support']) == 'default') {
342 $innodb_support = TRUE;
348 if ($innodb_support) {
349 $results['severity'] = $this::REQUIREMENT_OK
;
350 $results['details'] = 'MySQL supports InnoDB';
356 * @param array $db_config
360 public function checkMysqlTempTables(array $db_config) {
362 'title' => 'CiviCRM MySQL Temp Tables',
363 'severity' => $this::REQUIREMENT_OK
,
364 'details' => 'MySQL server supports temporary tables',
367 $conn = $this->connect($db_config);
369 $results['severity'] = $this::REQUIREMENT_ERROR
;
370 $results['details'] = "Could not connect to database";
374 if (!@mysqli_select_db
($conn, $db_config['database'])) {
375 $results['severity'] = $this::REQUIREMENT_ERROR
;
376 $results['details'] = "Could not select the database";
379 $temporaryTableName = \CRM_Utils_SQL_TempTable
::build()->setCategory('install')->getName();
380 $r = mysqli_query($conn, 'CREATE TEMPORARY TABLE ' . $temporaryTableName . ' (test text)');
382 $results['severity'] = $this::REQUIREMENT_ERROR
;
383 $results['details'] = "Database does not support creation of temporary tables";
387 mysqli_query($conn, 'DROP TEMPORARY TABLE ' . $temporaryTableName);
396 public function checkMysqlTrigger($db_config) {
398 'title' => 'CiviCRM MySQL Trigger',
399 'severity' => $this::REQUIREMENT_OK
,
400 'details' => 'Database supports MySQL triggers',
403 $conn = $this->connect($db_config);
405 $results['severity'] = $this::REQUIREMENT_ERROR
;
406 $results['details'] = 'Could not connect to database';
410 if (!@mysqli_select_db
($conn, $db_config['database'])) {
411 $results['severity'] = $this::REQUIREMENT_ERROR
;
412 $results['details'] = "Could not select the database";
416 $r = mysqli_query($conn, 'CREATE TABLE civicrm_install_temp_table_test (test text)');
418 $results['severity'] = $this::REQUIREMENT_ERROR
;
419 $results['details'] = 'Could not create a table to run test';
423 $r = mysqli_query($conn, 'CREATE TRIGGER civicrm_install_temp_table_test_trigger BEFORE INSERT ON civicrm_install_temp_table_test FOR EACH ROW BEGIN END');
425 $results['severity'] = $this::REQUIREMENT_ERROR
;
426 $results['details'] = 'Database does not support creation of triggers';
429 mysqli_query($conn, 'DROP TRIGGER civicrm_install_temp_table_test_trigger');
432 mysqli_query($conn, 'DROP TABLE civicrm_install_temp_table_test');
437 * @param array $db_config
441 public function checkMySQLAutoIncrementIncrementOne(array $db_config) {
443 'title' => 'CiviCRM MySQL AutoIncrementIncrement',
444 'severity' => $this::REQUIREMENT_OK
,
445 'details' => 'MySQL server auto_increment_increment is 1',
448 $conn = $this->connect($db_config);
450 $results['severity'] = $this::REQUIREMENT_ERROR
;
451 $results['details'] = 'Could not connect to database';
455 $r = mysqli_query($conn, "SHOW variables like 'auto_increment_increment'");
457 $results['severity'] = $this::REQUIREMENT_ERROR
;
458 $results['details'] = 'Could not query database server variables';
462 $values = mysqli_fetch_row($r);
463 if ($values[1] != 1) {
464 $results['severity'] = $this::REQUIREMENT_ERROR
;
465 $results['details'] = 'MySQL server auto_increment_increment is not 1';
475 public function checkMysqlThreadStack($db_config) {
476 $min_thread_stack = 192;
479 'title' => 'CiviCRM Mysql thread stack',
480 'severity' => $this::REQUIREMENT_OK
,
481 'details' => 'MySQL thread_stack is OK',
484 $conn = $this->connect($db_config);
486 $results['severity'] = $this::REQUIREMENT_ERROR
;
487 $results['details'] = 'Could not connect to database';
491 if (!@mysqli_select_db
($conn, $db_config['database'])) {
492 $results['severity'] = $this::REQUIREMENT_ERROR
;
493 $results['details'] = 'Could not select the database';
498 $r = mysqli_query($conn, "SHOW VARIABLES LIKE 'thread_stack'");
500 $results['severity'] = $this::REQUIREMENT_ERROR
;
501 $results['details'] = 'Could not query thread_stack value';
504 $values = mysqli_fetch_row($r);
505 if ($values[1] < (1024 * $min_thread_stack)) {
506 $results['severity'] = $this::REQUIREMENT_ERROR
;
507 $results['details'] = 'MySQL thread_stack is ' . ($values[1] / 1024) . "kb (minimum required is {$min_thread_stack} kb";
519 public function checkMysqlLockTables($db_config) {
521 'title' => 'CiviCRM MySQL Lock Tables',
522 'severity' => $this::REQUIREMENT_OK
,
523 'details' => 'Can successfully lock and unlock tables',
526 $conn = $this->connect($db_config);
528 $results['severity'] = $this::REQUIREMENT_ERROR
;
529 $results['details'] = 'Could not connect to database';
533 if (!@mysqli_select_db
($conn, $db_config['database'])) {
534 $results['severity'] = $this::REQUIREMENT_ERROR
;
535 $results['details'] = 'Could not select the database';
540 $r = mysqli_query($conn, 'CREATE TEMPORARY TABLE civicrm_install_temp_table_test (test text)');
542 $results['severity'] = $this::REQUIREMENT_ERROR
;
543 $results['details'] = 'Could not create a table';
548 $r = mysqli_query($conn, 'LOCK TABLES civicrm_install_temp_table_test WRITE');
550 $results['severity'] = $this::REQUIREMENT_ERROR
;
551 $results['details'] = 'Could not obtain a write lock';
556 $r = mysqli_query($conn, 'UNLOCK TABLES');
558 $results['severity'] = $this::REQUIREMENT_ERROR
;
559 $results['details'] = 'Could not release table lock';
571 public function checkFilepathIsWritable($file_paths) {
573 'title' => 'CiviCRM directories are writable',
574 'severity' => $this::REQUIREMENT_OK
,
575 'details' => 'All required directories are writable: ' . implode(', ', $file_paths),
578 $unwritable_dirs = [];
579 foreach ($file_paths as $path) {
580 if (!is_writable($path)) {
581 $unwritable_dirs[] = $path;
585 if ($unwritable_dirs) {
586 $results['severity'] = $this::REQUIREMENT_ERROR
;
587 $results['details'] = "The following directories need to be made writable by the webserver: " . implode(', ', $unwritable_dirs);
598 public function checkMysqlUtf8mb4($db_config) {
600 'title' => 'CiviCRM MySQL utf8mb4 Support',
601 'severity' => $this::REQUIREMENT_OK
,
602 'details' => 'Your system supports the MySQL utf8mb4 character set.',
605 $conn = $this->connect($db_config);
607 $results['severity'] = $this::REQUIREMENT_ERROR
;
608 $results['details'] = 'Could not connect to database';
612 if (!@mysqli_select_db
($conn, $db_config['database'])) {
613 $results['severity'] = $this::REQUIREMENT_ERROR
;
614 $results['details'] = 'Could not select the database';
619 mysqli_query($conn, 'DROP TABLE IF EXISTS civicrm_utf8mb4_test');
620 $r = 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');
622 $results['severity'] = $this::REQUIREMENT_WARNING
;
623 $results['details'] = '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';
627 mysqli_query($conn, 'DROP TABLE civicrm_utf8mb4_test');
629 // Ensure that the MySQL driver supports utf8mb4 encoding.
630 $version = mysqli_get_client_info();
631 if (strpos($version, 'mysqlnd') !== FALSE) {
632 // The mysqlnd driver supports utf8mb4 starting at version 5.0.9.
633 $version = preg_replace('/^\D+([\d.]+).*/', '$1', $version);
634 if (version_compare($version, '5.0.9', '<')) {
635 $results['severity'] = $this::REQUIREMENT_WARNING
;
636 $results['details'] = 'It is recommended, though not yet required, to upgrade your PHP MySQL driver (mysqlnd) to >= 5.0.9 for utf8mb4 support.';
642 // The libmysqlclient driver supports utf8mb4 starting at version 5.5.3.
643 if (version_compare($version, '5.5.3', '<')) {
644 $results['severity'] = $this::REQUIREMENT_WARNING
;
645 $results['details'] = 'It is recommended, though not yet required, to upgrade your PHP MySQL driver (libmysqlclient) to >= 5.5.3 for utf8mb4 support.';