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 $versionDetails = mysqli_query($conn, 'SELECT version() as version')->fetch_assoc();
311 if (version_compare($versionDetails['version'], $min) == -1) {
312 $results['severity'] = $this::REQUIREMENT_ERROR
;
313 $results['details'] = "MySQL version is {$info}; minimum required is {$min}";
317 $results['details'] = "MySQL version is {$info}";
322 * @param array $db_config
326 public function checkMysqlInnodb(array $db_config) {
328 'title' => 'CiviCRM InnoDB support',
329 'severity' => $this::REQUIREMENT_ERROR
,
330 'details' => 'Could not determine if MySQL has InnoDB support. Assuming none.',
333 $conn = $this->connect($db_config);
338 $innodb_support = FALSE;
339 $result = mysqli_query($conn, "SHOW ENGINES");
340 while ($values = mysqli_fetch_array($result)) {
341 if ($values['Engine'] == 'InnoDB') {
342 if (strtolower($values['Support']) == 'yes' ||
strtolower($values['Support']) == 'default') {
343 $innodb_support = TRUE;
349 if ($innodb_support) {
350 $results['severity'] = $this::REQUIREMENT_OK
;
351 $results['details'] = 'MySQL supports InnoDB';
357 * @param array $db_config
361 public function checkMysqlTempTables(array $db_config) {
363 'title' => 'CiviCRM MySQL Temp Tables',
364 'severity' => $this::REQUIREMENT_OK
,
365 'details' => 'MySQL server supports temporary tables',
368 $conn = $this->connect($db_config);
370 $results['severity'] = $this::REQUIREMENT_ERROR
;
371 $results['details'] = "Could not connect to database";
375 if (!@mysqli_select_db
($conn, $db_config['database'])) {
376 $results['severity'] = $this::REQUIREMENT_ERROR
;
377 $results['details'] = "Could not select the database";
380 $temporaryTableName = \CRM_Utils_SQL_TempTable
::build()->setCategory('install')->getName();
381 $r = mysqli_query($conn, 'CREATE TEMPORARY TABLE ' . $temporaryTableName . ' (test text)');
383 $results['severity'] = $this::REQUIREMENT_ERROR
;
384 $results['details'] = "Database does not support creation of temporary tables";
388 mysqli_query($conn, 'DROP TEMPORARY TABLE ' . $temporaryTableName);
397 public function checkMysqlTrigger($db_config) {
399 'title' => 'CiviCRM MySQL Trigger',
400 'severity' => $this::REQUIREMENT_OK
,
401 'details' => 'Database supports MySQL triggers',
404 $conn = $this->connect($db_config);
406 $results['severity'] = $this::REQUIREMENT_ERROR
;
407 $results['details'] = 'Could not connect to database';
411 if (!@mysqli_select_db
($conn, $db_config['database'])) {
412 $results['severity'] = $this::REQUIREMENT_ERROR
;
413 $results['details'] = "Could not select the database";
417 $r = mysqli_query($conn, 'CREATE TABLE civicrm_install_temp_table_test (test text)');
419 $results['severity'] = $this::REQUIREMENT_ERROR
;
420 $results['details'] = 'Could not create a table to run test';
424 $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');
426 $results['severity'] = $this::REQUIREMENT_ERROR
;
427 $results['details'] = 'Database does not support creation of triggers';
430 mysqli_query($conn, 'DROP TRIGGER civicrm_install_temp_table_test_trigger');
433 mysqli_query($conn, 'DROP TABLE civicrm_install_temp_table_test');
438 * @param array $db_config
442 public function checkMySQLAutoIncrementIncrementOne(array $db_config) {
444 'title' => 'CiviCRM MySQL AutoIncrementIncrement',
445 'severity' => $this::REQUIREMENT_OK
,
446 'details' => 'MySQL server auto_increment_increment is 1',
449 $conn = $this->connect($db_config);
451 $results['severity'] = $this::REQUIREMENT_ERROR
;
452 $results['details'] = 'Could not connect to database';
456 $r = mysqli_query($conn, "SHOW variables like 'auto_increment_increment'");
458 $results['severity'] = $this::REQUIREMENT_ERROR
;
459 $results['details'] = 'Could not query database server variables';
463 $values = mysqli_fetch_row($r);
464 if ($values[1] != 1) {
465 $results['severity'] = $this::REQUIREMENT_ERROR
;
466 $results['details'] = 'MySQL server auto_increment_increment is not 1';
476 public function checkMysqlThreadStack($db_config) {
477 $min_thread_stack = 192;
480 'title' => 'CiviCRM Mysql thread stack',
481 'severity' => $this::REQUIREMENT_OK
,
482 'details' => 'MySQL thread_stack is OK',
485 $conn = $this->connect($db_config);
487 $results['severity'] = $this::REQUIREMENT_ERROR
;
488 $results['details'] = 'Could not connect to database';
492 if (!@mysqli_select_db
($conn, $db_config['database'])) {
493 $results['severity'] = $this::REQUIREMENT_ERROR
;
494 $results['details'] = 'Could not select the database';
499 $r = mysqli_query($conn, "SHOW VARIABLES LIKE 'thread_stack'");
501 $results['severity'] = $this::REQUIREMENT_ERROR
;
502 $results['details'] = 'Could not query thread_stack value';
505 $values = mysqli_fetch_row($r);
506 if ($values[1] < (1024 * $min_thread_stack)) {
507 $results['severity'] = $this::REQUIREMENT_ERROR
;
508 $results['details'] = 'MySQL thread_stack is ' . ($values[1] / 1024) . "kb (minimum required is {$min_thread_stack} kb";
520 public function checkMysqlLockTables($db_config) {
522 'title' => 'CiviCRM MySQL Lock Tables',
523 'severity' => $this::REQUIREMENT_OK
,
524 'details' => 'Can successfully lock and unlock tables',
527 $conn = $this->connect($db_config);
529 $results['severity'] = $this::REQUIREMENT_ERROR
;
530 $results['details'] = 'Could not connect to database';
534 if (!@mysqli_select_db
($conn, $db_config['database'])) {
535 $results['severity'] = $this::REQUIREMENT_ERROR
;
536 $results['details'] = 'Could not select the database';
541 $r = mysqli_query($conn, 'CREATE TEMPORARY TABLE civicrm_install_temp_table_test (test text)');
543 $results['severity'] = $this::REQUIREMENT_ERROR
;
544 $results['details'] = 'Could not create a table';
549 $r = mysqli_query($conn, 'LOCK TABLES civicrm_install_temp_table_test WRITE');
551 $results['severity'] = $this::REQUIREMENT_ERROR
;
552 $results['details'] = 'Could not obtain a write lock';
557 $r = mysqli_query($conn, 'UNLOCK TABLES');
559 $results['severity'] = $this::REQUIREMENT_ERROR
;
560 $results['details'] = 'Could not release table lock';
572 public function checkFilepathIsWritable($file_paths) {
574 'title' => 'CiviCRM directories are writable',
575 'severity' => $this::REQUIREMENT_OK
,
576 'details' => 'All required directories are writable: ' . implode(', ', $file_paths),
579 $unwritable_dirs = [];
580 foreach ($file_paths as $path) {
581 if (!is_writable($path)) {
582 $unwritable_dirs[] = $path;
586 if ($unwritable_dirs) {
587 $results['severity'] = $this::REQUIREMENT_ERROR
;
588 $results['details'] = "The following directories need to be made writable by the webserver: " . implode(', ', $unwritable_dirs);
599 public function checkMysqlUtf8mb4($db_config) {
601 'title' => 'CiviCRM MySQL utf8mb4 Support',
602 'severity' => $this::REQUIREMENT_OK
,
603 'details' => 'Your system supports the MySQL utf8mb4 character set.',
606 $conn = $this->connect($db_config);
608 $results['severity'] = $this::REQUIREMENT_ERROR
;
609 $results['details'] = 'Could not connect to database';
613 if (!@mysqli_select_db
($conn, $db_config['database'])) {
614 $results['severity'] = $this::REQUIREMENT_ERROR
;
615 $results['details'] = 'Could not select the database';
620 mysqli_query($conn, 'DROP TABLE IF EXISTS civicrm_utf8mb4_test');
621 $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');
623 $results['severity'] = $this::REQUIREMENT_WARNING
;
624 $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';
628 mysqli_query($conn, 'DROP TABLE civicrm_utf8mb4_test');
630 // Ensure that the MySQL driver supports utf8mb4 encoding.
631 $version = mysqli_get_client_info();
632 if (strpos($version, 'mysqlnd') !== FALSE) {
633 // The mysqlnd driver supports utf8mb4 starting at version 5.0.9.
634 $version = preg_replace('/^\D+([\d.]+).*/', '$1', $version);
635 if (version_compare($version, '5.0.9', '<')) {
636 $results['severity'] = $this::REQUIREMENT_WARNING
;
637 $results['details'] = 'It is recommended, though not yet required, to upgrade your PHP MySQL driver (mysqlnd) to >= 5.0.9 for utf8mb4 support.';
643 // The libmysqlclient driver supports utf8mb4 starting at version 5.5.3.
644 if (version_compare($version, '5.5.3', '<')) {
645 $results['severity'] = $this::REQUIREMENT_WARNING
;
646 $results['details'] = 'It is recommended, though not yet required, to upgrade your PHP MySQL driver (libmysqlclient) to >= 5.5.3 for utf8mb4 support.';