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 return array_merge($this->checkSystem($config['file_paths']), $this->checkDatabase($config['db_config']));
65 * Check system requirements are met, such as sufficient memory,
66 * necessary file paths are writable and required php extensions
69 * @param array $file_paths
70 * An array of file paths that will be checked to confirm they
75 public function checkSystem(array $file_paths) {
78 $errors[] = $this->checkFilepathIsWritable($file_paths);
79 foreach ($this->system_checks
as $check) {
80 $errors[] = $this->$check();
87 * Check database connection, database version and other
88 * database requirements are met.
90 * @param array $db_config
92 * - host (with optional port specified eg. localhost:12345)
93 * - database (name of database to select)
99 public function checkDatabase(array $db_config) {
102 foreach ($this->database_checks
as $check) {
103 $errors[] = $this->$check($db_config);
110 * Generates a mysql connection
112 * @param $db_config array
113 * @return object mysqli connection
115 protected function connect($db_config) {
117 if (!empty($db_config['host'])) {
118 $host = $db_config['host'];
120 elseif (!empty($db_config['server'])) {
121 $host = $db_config['server'];
123 $conn = @mysqli_connect
($host, $db_config['username'], $db_config['password'], $db_config['database'], !empty($db_config['port']) ?
$db_config['port'] : NULL);
128 * Check configured php Memory.
131 public function checkMemory() {
132 $min = 1024 * 1024 * 32;
133 $recommended = 1024 * 1024 * 64;
135 $mem = $this->getPHPMemory();
136 $mem_string = ini_get('memory_limit');
139 'title' => 'CiviCRM memory check',
140 'severity' => $this::REQUIREMENT_OK
,
141 'details' => "You have $mem_string allocated (minimum 32Mb, recommended 64Mb)",
144 if ($mem < $min && $mem > 0) {
145 $results['severity'] = $this::REQUIREMENT_ERROR
;
147 elseif ($mem < $recommended && $mem != 0) {
148 $results['severity'] = $this::REQUIREMENT_WARNING
;
151 $results['details'] = "Cannot determine PHP memory allocation. Install only if you're sure you've allocated at least 32 MB.";
152 $results['severity'] = $this::REQUIREMENT_WARNING
;
159 * Get Configured PHP memory.
162 protected function getPHPMemory() {
163 $memString = ini_get("memory_limit");
165 switch (strtolower(substr($memString, -1))) {
167 return round(substr($memString, 0, -1) * 1024);
170 return round(substr($memString, 0, -1) * 1024 * 1024);
173 return round(substr($memString, 0, -1) * 1024 * 1024 * 1024);
176 return round($memString);
183 public function checkServerVariables() {
185 'title' => 'CiviCRM PHP server variables',
186 'severity' => $this::REQUIREMENT_OK
,
187 'details' => 'The required $_SERVER variables are set',
190 $required_variables = ['SCRIPT_NAME', 'HTTP_HOST', 'SCRIPT_FILENAME'];
193 foreach ($required_variables as $required_variable) {
194 if (empty($_SERVER[$required_variable])) {
195 $missing[] = '$_SERVER[' . $required_variable . ']';
200 $results['severity'] = $this::REQUIREMENT_ERROR
;
201 $results['details'] = 'The following PHP variables are not set: ' . implode(', ', $missing);
210 public function checkJsonEncodeExists() {
212 'title' => 'CiviCRM JSON encoding support',
213 'severity' => $this::REQUIREMENT_OK
,
214 'details' => 'Function json_encode() found',
216 if (!function_exists('json_encode')) {
217 $results['severity'] = $this::REQUIREMENT_ERROR
;
218 $results['details'] = 'Function json_encode() does not exist';
225 * CHeck that PHP Multibyte functions are enabled.
228 public function checkMultibyteExists() {
230 'title' => 'CiviCRM MultiByte encoding support',
231 'severity' => $this::REQUIREMENT_OK
,
232 'details' => 'PHP Multibyte etension found',
234 if (!function_exists('mb_substr')) {
235 $results['severity'] = $this::REQUIREMENT_ERROR
;
236 $results['details'] = 'PHP Multibyte extension has not been installed and enabled';
245 public function checkMysqlConnectExists() {
247 'title' => 'CiviCRM MySQL check',
248 'severity' => $this::REQUIREMENT_OK
,
249 'details' => 'Function mysqli_connect() found',
251 if (!function_exists('mysqli_connect')) {
252 $results['severity'] = $this::REQUIREMENT_ERROR
;
253 $results['details'] = 'Function mysqli_connect() does not exist';
260 * @param array $db_config
264 public function checkMysqlConnection(array $db_config) {
266 'title' => 'CiviCRM MySQL connection',
267 'severity' => $this::REQUIREMENT_OK
,
268 'details' => "Connected",
271 $conn = $this->connect($db_config);
274 $results['details'] = mysqli_connect_error();
275 $results['severity'] = $this::REQUIREMENT_ERROR
;
279 if (!@mysqli_select_db
($conn, $db_config['database'])) {
280 $results['details'] = mysqli_error($conn);
281 $results['severity'] = $this::REQUIREMENT_ERROR
;
289 * @param array $db_config
293 public function checkMysqlVersion(array $db_config) {
296 'title' => 'CiviCRM MySQL Version',
297 'severity' => $this::REQUIREMENT_OK
,
300 $conn = $this->connect($db_config);
301 if (!$conn ||
!($info = mysqli_get_server_info($conn))) {
302 $results['severity'] = $this::REQUIREMENT_WARNING
;
303 $results['details'] = "Cannot determine the version of MySQL installed. Please ensure at least version {$min} is installed.";
307 if (version_compare($info, $min) == -1) {
308 $results['severity'] = $this::REQUIREMENT_ERROR
;
309 $results['details'] = "MySQL version is {$info}; minimum required is {$min}";
313 $results['details'] = "MySQL version is {$info}";
318 * @param array $db_config
322 public function checkMysqlInnodb(array $db_config) {
324 'title' => 'CiviCRM InnoDB support',
325 'severity' => $this::REQUIREMENT_ERROR
,
326 'details' => 'Could not determine if MySQL has InnoDB support. Assuming none.',
329 $conn = $this->connect($db_config);
334 $innodb_support = FALSE;
335 $result = mysqli_query($conn, "SHOW ENGINES");
336 while ($values = mysqli_fetch_array($result)) {
337 if ($values['Engine'] == 'InnoDB') {
338 if (strtolower($values['Support']) == 'yes' ||
strtolower($values['Support']) == 'default') {
339 $innodb_support = TRUE;
345 if ($innodb_support) {
346 $results['severity'] = $this::REQUIREMENT_OK
;
347 $results['details'] = 'MySQL supports InnoDB';
353 * @param array $db_config
357 public function checkMysqlTempTables(array $db_config) {
359 'title' => 'CiviCRM MySQL Temp Tables',
360 'severity' => $this::REQUIREMENT_OK
,
361 'details' => 'MySQL server supports temporary tables',
364 $conn = $this->connect($db_config);
366 $results['severity'] = $this::REQUIREMENT_ERROR
;
367 $results['details'] = "Could not connect to database";
371 if (!@mysqli_select_db
($conn, $db_config['database'])) {
372 $results['severity'] = $this::REQUIREMENT_ERROR
;
373 $results['details'] = "Could not select the database";
377 $r = mysqli_query($conn, 'CREATE TEMPORARY TABLE civicrm_install_temp_table_test (test text)');
379 $results['severity'] = $this::REQUIREMENT_ERROR
;
380 $results['details'] = "Database does not support creation of temporary tables";
384 mysqli_query($conn, 'DROP TEMPORARY TABLE civicrm_install_temp_table_test');
393 public function checkMysqlTrigger($db_config) {
395 'title' => 'CiviCRM MySQL Trigger',
396 'severity' => $this::REQUIREMENT_OK
,
397 'details' => 'Database supports MySQL triggers',
400 $conn = $this->connect($db_config);
402 $results['severity'] = $this::REQUIREMENT_ERROR
;
403 $results['details'] = 'Could not connect to database';
407 if (!@mysqli_select_db
($conn, $db_config['database'])) {
408 $results['severity'] = $this::REQUIREMENT_ERROR
;
409 $results['details'] = "Could not select the database";
413 $r = mysqli_query($conn, 'CREATE TABLE civicrm_install_temp_table_test (test text)');
415 $results['severity'] = $this::REQUIREMENT_ERROR
;
416 $results['details'] = 'Could not create a table to run test';
420 $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');
422 $results['severity'] = $this::REQUIREMENT_ERROR
;
423 $results['details'] = 'Database does not support creation of triggers';
426 mysqli_query($conn, 'DROP TRIGGER civicrm_install_temp_table_test_trigger');
429 mysqli_query($conn, 'DROP TABLE civicrm_install_temp_table_test');
434 * @param array $db_config
438 public function checkMySQLAutoIncrementIncrementOne(array $db_config) {
440 'title' => 'CiviCRM MySQL AutoIncrementIncrement',
441 'severity' => $this::REQUIREMENT_OK
,
442 'details' => 'MySQL server auto_increment_increment is 1',
445 $conn = $this->connect($db_config);
447 $results['severity'] = $this::REQUIREMENT_ERROR
;
448 $results['details'] = 'Could not connect to database';
452 $r = mysqli_query($conn, "SHOW variables like 'auto_increment_increment'");
454 $results['severity'] = $this::REQUIREMENT_ERROR
;
455 $results['details'] = 'Could not query database server variables';
459 $values = mysqli_fetch_row($r);
460 if ($values[1] != 1) {
461 $results['severity'] = $this::REQUIREMENT_ERROR
;
462 $results['details'] = 'MySQL server auto_increment_increment is not 1';
472 public function checkMysqlThreadStack($db_config) {
473 $min_thread_stack = 192;
476 'title' => 'CiviCRM Mysql thread stack',
477 'severity' => $this::REQUIREMENT_OK
,
478 'details' => 'MySQL thread_stack is OK',
481 $conn = $this->connect($db_config);
483 $results['severity'] = $this::REQUIREMENT_ERROR
;
484 $results['details'] = 'Could not connect to database';
488 if (!@mysqli_select_db
($conn, $db_config['database'])) {
489 $results['severity'] = $this::REQUIREMENT_ERROR
;
490 $results['details'] = 'Could not select the database';
495 $r = mysqli_query($conn, "SHOW VARIABLES LIKE 'thread_stack'");
497 $results['severity'] = $this::REQUIREMENT_ERROR
;
498 $results['details'] = 'Could not query thread_stack value';
501 $values = mysqli_fetch_row($r);
502 if ($values[1] < (1024 * $min_thread_stack)) {
503 $results['severity'] = $this::REQUIREMENT_ERROR
;
504 $results['details'] = 'MySQL thread_stack is ' . ($values[1] / 1024) . "kb (minimum required is {$min_thread_stack} kb";
516 public function checkMysqlLockTables($db_config) {
518 'title' => 'CiviCRM MySQL Lock Tables',
519 'severity' => $this::REQUIREMENT_OK
,
520 'details' => 'Can successfully lock and unlock tables',
523 $conn = $this->connect($db_config);
525 $results['severity'] = $this::REQUIREMENT_ERROR
;
526 $results['details'] = 'Could not connect to database';
530 if (!@mysqli_select_db
($conn, $db_config['database'])) {
531 $results['severity'] = $this::REQUIREMENT_ERROR
;
532 $results['details'] = 'Could not select the database';
537 $r = mysqli_query($conn, 'CREATE TEMPORARY TABLE civicrm_install_temp_table_test (test text)');
539 $results['severity'] = $this::REQUIREMENT_ERROR
;
540 $results['details'] = 'Could not create a table';
545 $r = mysqli_query($conn, 'LOCK TABLES civicrm_install_temp_table_test WRITE');
547 $results['severity'] = $this::REQUIREMENT_ERROR
;
548 $results['details'] = 'Could not obtain a write lock';
553 $r = mysqli_query($conn, 'UNLOCK TABLES');
555 $results['severity'] = $this::REQUIREMENT_ERROR
;
556 $results['details'] = 'Could not release table lock';
568 public function checkFilepathIsWritable($file_paths) {
570 'title' => 'CiviCRM directories are writable',
571 'severity' => $this::REQUIREMENT_OK
,
572 'details' => 'All required directories are writable: ' . implode(', ', $file_paths),
575 $unwritable_dirs = [];
576 foreach ($file_paths as $path) {
577 if (!is_writable($path)) {
578 $unwritable_dirs[] = $path;
582 if ($unwritable_dirs) {
583 $results['severity'] = $this::REQUIREMENT_ERROR
;
584 $results['details'] = "The following directories need to be made writable by the webserver: " . implode(', ', $unwritable_dirs);
595 public function checkMysqlUtf8mb4($db_config) {
597 'title' => 'CiviCRM MySQL utf8mb4 Support',
598 'severity' => $this::REQUIREMENT_OK
,
599 'details' => 'Your system supports the MySQL utf8mb4 character set.',
602 $conn = $this->connect($db_config);
604 $results['severity'] = $this::REQUIREMENT_ERROR
;
605 $results['details'] = 'Could not connect to database';
609 if (!@mysqli_select_db
($conn, $db_config['database'])) {
610 $results['severity'] = $this::REQUIREMENT_ERROR
;
611 $results['details'] = 'Could not select the database';
616 $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');
618 $results['severity'] = $this::REQUIREMENT_WARNING
;
619 $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';
623 mysqli_query('DROP TABLE civicrm_utf8mb4_test');
625 // Ensure that the MySQL driver supports utf8mb4 encoding.
626 $version = mysqli_get_client_info($conn);
627 if (strpos($version, 'mysqlnd') !== FALSE) {
628 // The mysqlnd driver supports utf8mb4 starting at version 5.0.9.
629 $version = preg_replace('/^\D+([\d.]+).*/', '$1', $version);
630 if (version_compare($version, '5.0.9', '<')) {
631 $results['severity'] = $this::REQUIREMENT_WARNING
;
632 $results['details'] = 'It is recommended, though not yet required, to upgrade your PHP MySQL driver (mysqlnd) to >= 5.0.9 for utf8mb4 support.';
638 // The libmysqlclient driver supports utf8mb4 starting at version 5.5.3.
639 if (version_compare($version, '5.5.3', '<')) {
640 $results['severity'] = $this::REQUIREMENT_WARNING
;
641 $results['details'] = 'It is recommended, though not yet required, to upgrade your PHP MySQL driver (libmysqlclient) to >= 5.5.3 for utf8mb4 support.';