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 'checkMysqlConnectExists',
32 'checkJsonEncodeExists',
33 'checkMultibyteExists',
36 protected $system_checks_web = [
37 'checkServerVariables',
40 protected $database_checks = [
41 'checkMysqlConnection',
44 'checkMysqlTempTables',
45 'checkMySQLAutoIncrementIncrementOne',
47 'checkMysqlThreadStack',
48 'checkMysqlLockTables',
53 * Run all requirements tests.
55 * @param array $config
56 * An array with two keys:
61 * An array of check summaries. Each array contains the keys 'title', 'severity', and 'details'.
63 public function checkAll(array $config) {
64 if (!class_exists('\CRM_Utils_SQL_TempTable')) {
65 require_once dirname(__FILE__
) . '/../../CRM/Utils/SQL/TempTable.php';
67 return array_merge($this->checkSystem($config['file_paths']), $this->checkDatabase($config['db_config']));
71 * Check system requirements are met, such as sufficient memory,
72 * necessary file paths are writable and required php extensions
75 * @param array $file_paths
76 * An array of file paths that will be checked to confirm they
81 public function checkSystem(array $file_paths) {
84 $errors[] = $this->checkFilepathIsWritable($file_paths);
85 foreach ($this->system_checks
as $check) {
86 $errors[] = $this->$check();
89 if (PHP_SAPI
!== 'cli') {
90 foreach ($this->system_checks_web
as $check) {
91 $errors[] = $this->$check();
99 * Check database connection, database version and other
100 * database requirements are met.
102 * @param array $db_config
103 * An array with keys:
104 * - host (with optional port specified eg. localhost:12345)
105 * - database (name of database to select)
111 public function checkDatabase(array $db_config) {
114 foreach ($this->database_checks
as $check) {
115 $errors[] = $this->$check($db_config);
122 * Generates a mysql connection
124 * @param $db_config array
125 * @return object mysqli connection
127 protected function connect($db_config) {
129 if (!empty($db_config['host'])) {
130 $host = $db_config['host'];
132 elseif (!empty($db_config['server'])) {
133 $host = $db_config['server'];
135 $conn = @mysqli_connect
($host, $db_config['username'], $db_config['password'], $db_config['database'], !empty($db_config['port']) ?
$db_config['port'] : NULL);
140 * Check configured php Memory.
143 public function checkMemory() {
144 $min = 1024 * 1024 * 32;
145 $recommended = 1024 * 1024 * 64;
147 $mem = $this->getPHPMemory();
148 $mem_string = ini_get('memory_limit');
151 'title' => 'CiviCRM memory check',
152 'severity' => $this::REQUIREMENT_OK
,
153 'details' => "You have $mem_string allocated (minimum 32Mb, recommended 64Mb)",
156 if ($mem < $min && $mem > 0) {
157 $results['severity'] = $this::REQUIREMENT_ERROR
;
159 elseif ($mem < $recommended && $mem != 0) {
160 $results['severity'] = $this::REQUIREMENT_WARNING
;
163 $results['details'] = "Cannot determine PHP memory allocation. Install only if you're sure you've allocated at least 32 MB.";
164 $results['severity'] = $this::REQUIREMENT_WARNING
;
171 * Get Configured PHP memory.
174 protected function getPHPMemory() {
175 $memString = ini_get("memory_limit");
177 switch (strtolower(substr($memString, -1))) {
179 return round(substr($memString, 0, -1) * 1024);
182 return round(substr($memString, 0, -1) * 1024 * 1024);
185 return round(substr($memString, 0, -1) * 1024 * 1024 * 1024);
188 return round($memString);
195 public function checkServerVariables() {
197 'title' => 'CiviCRM PHP server variables',
198 'severity' => $this::REQUIREMENT_OK
,
199 'details' => 'The required $_SERVER variables are set',
202 $required_variables = ['SCRIPT_NAME', 'HTTP_HOST', 'SCRIPT_FILENAME'];
205 foreach ($required_variables as $required_variable) {
206 if (empty($_SERVER[$required_variable])) {
207 $missing[] = '$_SERVER[' . $required_variable . ']';
212 $results['severity'] = $this::REQUIREMENT_ERROR
;
213 $results['details'] = 'The following PHP variables are not set: ' . implode(', ', $missing);
222 public function checkJsonEncodeExists() {
224 'title' => 'CiviCRM JSON encoding support',
225 'severity' => $this::REQUIREMENT_OK
,
226 'details' => 'Function json_encode() found',
228 if (!function_exists('json_encode')) {
229 $results['severity'] = $this::REQUIREMENT_ERROR
;
230 $results['details'] = 'Function json_encode() does not exist';
237 * CHeck that PHP Multibyte functions are enabled.
240 public function checkMultibyteExists() {
242 'title' => 'CiviCRM MultiByte encoding support',
243 'severity' => $this::REQUIREMENT_OK
,
244 'details' => 'PHP Multibyte etension found',
246 if (!function_exists('mb_substr')) {
247 $results['severity'] = $this::REQUIREMENT_ERROR
;
248 $results['details'] = 'PHP Multibyte extension has not been installed and enabled';
257 public function checkMysqlConnectExists() {
259 'title' => 'CiviCRM MySQL check',
260 'severity' => $this::REQUIREMENT_OK
,
261 'details' => 'Function mysqli_connect() found',
263 if (!function_exists('mysqli_connect')) {
264 $results['severity'] = $this::REQUIREMENT_ERROR
;
265 $results['details'] = 'Function mysqli_connect() does not exist';
272 * @param array $db_config
276 public function checkMysqlConnection(array $db_config) {
278 'title' => 'CiviCRM MySQL connection',
279 'severity' => $this::REQUIREMENT_OK
,
280 'details' => "Connected",
283 $conn = $this->connect($db_config);
286 $results['details'] = mysqli_connect_error();
287 $results['severity'] = $this::REQUIREMENT_ERROR
;
291 if (!@mysqli_select_db
($conn, $db_config['database'])) {
292 $results['details'] = mysqli_error($conn);
293 $results['severity'] = $this::REQUIREMENT_ERROR
;
301 * @param array $db_config
305 public function checkMysqlVersion(array $db_config) {
306 $min = \CRM_Upgrade_Incremental_General
::MIN_INSTALL_MYSQL_VER
;
308 'title' => 'CiviCRM MySQL Version',
309 'severity' => $this::REQUIREMENT_OK
,
312 $conn = $this->connect($db_config);
313 if (!$conn ||
!($info = mysqli_get_server_info($conn))) {
314 $results['severity'] = $this::REQUIREMENT_WARNING
;
315 $results['details'] = "Cannot determine the version of MySQL installed. Please ensure at least version {$min} is installed.";
319 $versionDetails = mysqli_query($conn, 'SELECT version() as version')->fetch_assoc();
320 if (version_compare($versionDetails['version'], $min) == -1) {
321 $results['severity'] = $this::REQUIREMENT_ERROR
;
322 $results['details'] = "MySQL version is {$info}; minimum required is {$min}";
326 $results['details'] = "MySQL version is {$info}";
331 * @param array $db_config
335 public function checkMysqlInnodb(array $db_config) {
337 'title' => 'CiviCRM InnoDB support',
338 'severity' => $this::REQUIREMENT_ERROR
,
339 'details' => 'Could not determine if MySQL has InnoDB support. Assuming none.',
342 $conn = $this->connect($db_config);
347 $innodb_support = FALSE;
348 $result = mysqli_query($conn, "SHOW ENGINES");
349 while ($values = mysqli_fetch_array($result)) {
350 if ($values['Engine'] == 'InnoDB') {
351 if (strtolower($values['Support']) == 'yes' ||
strtolower($values['Support']) == 'default') {
352 $innodb_support = TRUE;
358 if ($innodb_support) {
359 $results['severity'] = $this::REQUIREMENT_OK
;
360 $results['details'] = 'MySQL supports InnoDB';
366 * @param array $db_config
370 public function checkMysqlTempTables(array $db_config) {
372 'title' => 'CiviCRM MySQL Temp Tables',
373 'severity' => $this::REQUIREMENT_OK
,
374 'details' => 'MySQL server supports temporary tables',
377 $conn = $this->connect($db_config);
379 $results['severity'] = $this::REQUIREMENT_ERROR
;
380 $results['details'] = "Could not connect to database";
384 if (!@mysqli_select_db
($conn, $db_config['database'])) {
385 $results['severity'] = $this::REQUIREMENT_ERROR
;
386 $results['details'] = "Could not select the database";
389 $temporaryTableName = \CRM_Utils_SQL_TempTable
::build()->setCategory('install')->getName();
390 $r = mysqli_query($conn, 'CREATE TEMPORARY TABLE ' . $temporaryTableName . ' (test text)');
392 $results['severity'] = $this::REQUIREMENT_ERROR
;
393 $results['details'] = "Database does not support creation of temporary tables";
397 mysqli_query($conn, 'DROP TEMPORARY TABLE ' . $temporaryTableName);
406 public function checkMysqlTrigger($db_config) {
408 'title' => 'CiviCRM MySQL Trigger',
409 'severity' => $this::REQUIREMENT_OK
,
410 'details' => 'Database supports MySQL triggers',
413 $conn = $this->connect($db_config);
415 $results['severity'] = $this::REQUIREMENT_ERROR
;
416 $results['details'] = 'Could not connect to database';
420 if (!@mysqli_select_db
($conn, $db_config['database'])) {
421 $results['severity'] = $this::REQUIREMENT_ERROR
;
422 $results['details'] = "Could not select the database";
426 $r = mysqli_query($conn, 'CREATE TABLE civicrm_install_temp_table_test (test text)');
428 $results['severity'] = $this::REQUIREMENT_ERROR
;
429 $results['details'] = 'Could not create a table to run test';
433 $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');
435 $results['severity'] = $this::REQUIREMENT_ERROR
;
436 $results['details'] = 'Database does not support creation of triggers';
439 mysqli_query($conn, 'DROP TRIGGER civicrm_install_temp_table_test_trigger');
442 mysqli_query($conn, 'DROP TABLE civicrm_install_temp_table_test');
447 * @param array $db_config
451 public function checkMySQLAutoIncrementIncrementOne(array $db_config) {
453 'title' => 'CiviCRM MySQL AutoIncrementIncrement',
454 'severity' => $this::REQUIREMENT_OK
,
455 'details' => 'MySQL server auto_increment_increment is 1',
458 $conn = $this->connect($db_config);
460 $results['severity'] = $this::REQUIREMENT_ERROR
;
461 $results['details'] = 'Could not connect to database';
465 $r = mysqli_query($conn, "SHOW variables like 'auto_increment_increment'");
467 $results['severity'] = $this::REQUIREMENT_ERROR
;
468 $results['details'] = 'Could not query database server variables';
472 $values = mysqli_fetch_row($r);
473 if ($values[1] != 1) {
474 $results['severity'] = $this::REQUIREMENT_ERROR
;
475 $results['details'] = 'MySQL server auto_increment_increment is not 1';
485 public function checkMysqlThreadStack($db_config) {
486 $min_thread_stack = 192;
489 'title' => 'CiviCRM Mysql thread stack',
490 'severity' => $this::REQUIREMENT_OK
,
491 'details' => 'MySQL thread_stack is OK',
494 $conn = $this->connect($db_config);
496 $results['severity'] = $this::REQUIREMENT_ERROR
;
497 $results['details'] = 'Could not connect to database';
501 if (!@mysqli_select_db
($conn, $db_config['database'])) {
502 $results['severity'] = $this::REQUIREMENT_ERROR
;
503 $results['details'] = 'Could not select the database';
508 $r = mysqli_query($conn, "SHOW VARIABLES LIKE 'thread_stack'");
510 $results['severity'] = $this::REQUIREMENT_ERROR
;
511 $results['details'] = 'Could not query thread_stack value';
514 $values = mysqli_fetch_row($r);
515 if ($values[1] < (1024 * $min_thread_stack)) {
516 $results['severity'] = $this::REQUIREMENT_ERROR
;
517 $results['details'] = 'MySQL thread_stack is ' . ($values[1] / 1024) . "kb (minimum required is {$min_thread_stack} kb";
529 public function checkMysqlLockTables($db_config) {
531 'title' => 'CiviCRM MySQL Lock Tables',
532 'severity' => $this::REQUIREMENT_OK
,
533 'details' => 'Can successfully lock and unlock tables',
536 $conn = $this->connect($db_config);
538 $results['severity'] = $this::REQUIREMENT_ERROR
;
539 $results['details'] = 'Could not connect to database';
543 if (!@mysqli_select_db
($conn, $db_config['database'])) {
544 $results['severity'] = $this::REQUIREMENT_ERROR
;
545 $results['details'] = 'Could not select the database';
550 $r = mysqli_query($conn, 'CREATE TEMPORARY TABLE civicrm_install_temp_table_test (test text)');
552 $results['severity'] = $this::REQUIREMENT_ERROR
;
553 $results['details'] = 'Could not create a table';
558 $r = mysqli_query($conn, 'LOCK TABLES civicrm_install_temp_table_test WRITE');
560 $results['severity'] = $this::REQUIREMENT_ERROR
;
561 $results['details'] = 'Could not obtain a write lock';
566 $r = mysqli_query($conn, 'UNLOCK TABLES');
568 $results['severity'] = $this::REQUIREMENT_ERROR
;
569 $results['details'] = 'Could not release table lock';
581 public function checkFilepathIsWritable($file_paths) {
583 'title' => 'CiviCRM directories are writable',
584 'severity' => $this::REQUIREMENT_OK
,
585 'details' => 'All required directories are writable: ' . implode(', ', $file_paths),
588 $unwritable_dirs = [];
589 foreach ($file_paths as $path) {
590 if (!is_writable($path)) {
591 $unwritable_dirs[] = $path;
595 if ($unwritable_dirs) {
596 $results['severity'] = $this::REQUIREMENT_ERROR
;
597 $results['details'] = "The following directories need to be made writable by the webserver: " . implode(', ', $unwritable_dirs);
608 public function checkMysqlUtf8mb4($db_config) {
610 'title' => 'CiviCRM MySQL utf8mb4 Support',
611 'severity' => $this::REQUIREMENT_OK
,
612 'details' => 'Your system supports the MySQL utf8mb4 character set.',
615 $conn = $this->connect($db_config);
617 $results['severity'] = $this::REQUIREMENT_ERROR
;
618 $results['details'] = 'Could not connect to database';
622 if (!@mysqli_select_db
($conn, $db_config['database'])) {
623 $results['severity'] = $this::REQUIREMENT_ERROR
;
624 $results['details'] = 'Could not select the database';
629 mysqli_query($conn, 'DROP TABLE IF EXISTS civicrm_utf8mb4_test');
630 $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');
632 $results['severity'] = $this::REQUIREMENT_WARNING
;
633 $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';
637 mysqli_query($conn, 'DROP TABLE civicrm_utf8mb4_test');
639 // Ensure that the MySQL driver supports utf8mb4 encoding.
640 $version = mysqli_get_client_info();
641 if (strpos($version, 'mysqlnd') !== FALSE) {
642 // The mysqlnd driver supports utf8mb4 starting at version 5.0.9.
643 $version = preg_replace('/^\D+([\d.]+).*/', '$1', $version);
644 if (version_compare($version, '5.0.9', '<')) {
645 $results['severity'] = $this::REQUIREMENT_WARNING
;
646 $results['details'] = 'It is recommended, though not yet required, to upgrade your PHP MySQL driver (mysqlnd) to >= 5.0.9 for utf8mb4 support.';
652 // The libmysqlclient driver supports utf8mb4 starting at version 5.5.3.
653 if (version_compare($version, '5.5.3', '<')) {
654 $results['severity'] = $this::REQUIREMENT_WARNING
;
655 $results['details'] = 'It is recommended, though not yet required, to upgrade your PHP MySQL driver (libmysqlclient) to >= 5.5.3 for utf8mb4 support.';