CRM-19835 Use host rather than server and put in port if supplied
[civicrm-core.git] / Civi / Install / Requirements.php
CommitLineData
d7dea16c
EM
1<?php
2
3namespace Civi\Install;
4
5/**
6 * Class Requirements
7 * @package Civi\Install
8 */
9class Requirements {
10
11 /**
12 * Requirement severity -- Requirement successfully met.
13 */
14 const REQUIREMENT_OK = 0;
15
16 /**
17 * Requirement severity -- Warning condition; proceed but flag warning.
18 */
19 const REQUIREMENT_WARNING = 1;
20
21 /**
22 * Requirement severity -- Error condition; abort installation.
23 */
24 const REQUIREMENT_ERROR = 2;
25
26 protected $system_checks = array(
27 'checkMemory',
28 'checkServerVariables',
29 'checkMysqlConnectExists',
30 'checkJsonEncodeExists',
31 );
32
33 protected $database_checks = array(
34 'checkMysqlConnection',
35 'checkMysqlVersion',
36 'checkMysqlInnodb',
37 'checkMysqlTempTables',
38 'checkMySQLAutoIncrementIncrementOne',
39 'checkMysqlTrigger',
40 'checkMysqlThreadStack',
41 'checkMysqlLockTables',
42 );
43
44 /**
45 * Run all requirements tests.
46 *
47 * @param array $config
48 * An array with two keys:
49 * - file_paths
50 * - db_config
51 *
a6c01b45
CW
52 * @return array
53 * An array of check summaries. Each array contains the keys 'title', 'severity', and 'details'.
d7dea16c
EM
54 */
55 public function checkAll(array $config) {
56 return array_merge($this->checkSystem($config['file_paths']), $this->checkDatabase($config['db_config']));
57 }
58
59 /**
60 * Check system requirements are met, such as sufficient memory,
61 * necessary file paths are writable and required php extensions
62 * are available.
63 *
64 * @param array $file_paths
65 * An array of file paths that will be checked to confirm they
66 * are writable.
67 *
68 * @return array
69 */
70 public function checkSystem(array $file_paths) {
71 $errors = array();
72
73 $errors[] = $this->checkFilepathIsWritable($file_paths);
74 foreach ($this->system_checks as $check) {
75 $errors[] = $this->$check();
76 }
77
78 return $errors;
79 }
80
81 /**
82 * Check database connection, database version and other
83 * database requirements are met.
84 *
85 * @param array $db_config
86 * An array with keys:
87 * - host (with optional port specified eg. localhost:12345)
16b10e64 88 * - database (name of database to select)
d7dea16c
EM
89 * - username
90 * - password
91 *
92 * @return array
93 */
94 public function checkDatabase(array $db_config) {
95 $errors = array();
96
97 foreach ($this->database_checks as $check) {
98 $errors[] = $this->$check($db_config);
99 }
100
101 return $errors;
102 }
103
9043c4bf
SL
104 /**
105 * Generates a mysql connection
106 *
107 * @param $db_confic array
108 * @return object mysqli connection
109 */
110 protected function connect($db_config) {
111 $conn = @mysqli_connect($db_config['host'], $db_config['username'], $db_config['password'], $db_config['database'], isset($db_config['port']) ? $db_config['port'] : NULL);
112 return $donn;
113 }
114
d7dea16c 115 /**
fe482240 116 * Check configured php Memory.
d7dea16c
EM
117 * @return array
118 */
119 public function checkMemory() {
120 $min = 1024 * 1024 * 32;
121 $recommended = 1024 * 1024 * 64;
122
123 $mem = $this->getPHPMemory();
124 $mem_string = ini_get('memory_limit');
125
126 $results = array(
127 'title' => 'CiviCRM memory check',
128 'severity' => $this::REQUIREMENT_OK,
129 'details' => "You have $mem_string allocated (minimum 32Mb, recommended 64Mb)",
130 );
131
132 if ($mem < $min && $mem > 0) {
133 $results['severity'] = $this::REQUIREMENT_ERROR;
134 }
4c9b6178 135 elseif ($mem < $recommended && $mem != 0) {
d7dea16c
EM
136 $results['severity'] = $this::REQUIREMENT_WARNING;
137 }
4c9b6178 138 elseif ($mem == 0) {
d7dea16c
EM
139 $results['details'] = "Cannot determine PHP memory allocation. Install only if you're sure you've allocated at least 32 MB.";
140 $results['severity'] = $this::REQUIREMENT_WARNING;
141 }
142
143 return $results;
144 }
145
146 /**
fe482240 147 * Get Configured PHP memory.
d7dea16c
EM
148 * @return float
149 */
150 protected function getPHPMemory() {
151 $memString = ini_get("memory_limit");
152
153 switch (strtolower(substr($memString, -1))) {
154 case "k":
155 return round(substr($memString, 0, -1) * 1024);
ea100cb5 156
d7dea16c
EM
157 case "m":
158 return round(substr($memString, 0, -1) * 1024 * 1024);
ea100cb5 159
d7dea16c
EM
160 case "g":
161 return round(substr($memString, 0, -1) * 1024 * 1024 * 1024);
ea100cb5 162
d7dea16c
EM
163 default:
164 return round($memString);
165 }
166 }
167
168 /**
169 * @return array
170 */
00be9182 171 public function checkServerVariables() {
d7dea16c
EM
172 $results = array(
173 'title' => 'CiviCRM PHP server variables',
174 'severity' => $this::REQUIREMENT_OK,
175 'details' => 'The required $_SERVER variables are set',
176 );
177
178 $required_variables = array('SCRIPT_NAME', 'HTTP_HOST', 'SCRIPT_FILENAME');
179 $missing = array();
180
181 foreach ($required_variables as $required_variable) {
182 if (empty($_SERVER[$required_variable])) {
183 $missing[] = '$_SERVER[' . $required_variable . ']';
184 }
185 }
186
187 if ($missing) {
188 $results['severity'] = $this::REQUIREMENT_ERROR;
189 $results['details'] = 'The following PHP variables are not set: ' . implode(', ', $missing);
190 }
191
192 return $results;
193 }
194
195 /**
196 * @return array
197 */
198 public function checkJsonEncodeExists() {
199 $results = array(
200 'title' => 'CiviCRM JSON encoding support',
201 'severity' => $this::REQUIREMENT_OK,
202 'details' => 'Function json_encode() found',
203 );
204 if (!function_exists('json_encode')) {
205 $results['severity'] = $this::REQUIREMENT_ERROR;
206 $results['details'] = 'Function json_encode() does not exist';
207 }
208
209 return $results;
210 }
211
212 /**
213 * @return array
214 */
215 public function checkMysqlConnectExists() {
216 $results = array(
217 'title' => 'CiviCRM MySQL check',
218 'severity' => $this::REQUIREMENT_OK,
fcf908c6 219 'details' => 'Function mysqli_connect() found',
d7dea16c 220 );
fcf908c6 221 if (!function_exists('mysqli_connect')) {
d7dea16c 222 $results['severity'] = $this::REQUIREMENT_ERROR;
fcf908c6 223 $results['details'] = 'Function mysqli_connect() does not exist';
d7dea16c
EM
224 }
225
226 return $results;
227 }
228
229 /**
230 * @param array $db_config
231 *
232 * @return array
233 */
234 public function checkMysqlConnection(array $db_config) {
235 $results = array(
236 'title' => 'CiviCRM MySQL connection',
237 'severity' => $this::REQUIREMENT_OK,
238 'details' => "Connected",
239 );
240
9043c4bf 241 $conn = $this->connect($db_config);
d7dea16c
EM
242
243 if (!$conn) {
fcf908c6 244 $results['details'] = mysqli_connect_error();
d7dea16c
EM
245 $results['severity'] = $this::REQUIREMENT_ERROR;
246 return $results;
247 }
248
fcf908c6 249 if (!@mysqli_select_db($conn, $db_config['database'])) {
250 $results['details'] = mysqli_error($conn);
d7dea16c
EM
251 $results['severity'] = $this::REQUIREMENT_ERROR;
252 return $results;
253 }
254
255 return $results;
256 }
257
258 /**
259 * @param array $db_config
260 *
261 * @return array
262 */
263 public function checkMysqlVersion(array $db_config) {
264 $min = '5.1';
265 $results = array(
266 'title' => 'CiviCRM MySQL Version',
267 'severity' => $this::REQUIREMENT_OK,
268 );
269
9043c4bf 270 $conn = $this->connect($db_config);
fcf908c6 271 if (!$conn || !($info = mysqli_get_server_info($conn))) {
d7dea16c
EM
272 $results['severity'] = $this::REQUIREMENT_WARNING;
273 $results['details'] = "Cannot determine the version of MySQL installed. Please ensure at least version {$min} is installed.";
274 return $results;
275 }
276
277 if (version_compare($info, $min) == -1) {
278 $results['severity'] = $this::REQUIREMENT_ERROR;
279 $results['details'] = "MySQL version is {$info}; minimum required is {$min}";
280 return $results;
281 }
282
283 $results['details'] = "MySQL version is {$info}";
284 return $results;
285 }
286
287 /**
288 * @param array $db_config
289 *
290 * @return array
291 */
292 public function checkMysqlInnodb(array $db_config) {
293 $results = array(
294 'title' => 'CiviCRM InnoDB support',
295 'severity' => $this::REQUIREMENT_ERROR,
87a890cc 296 'details' => 'Could not determine if MySQL has InnoDB support. Assuming none.',
d7dea16c
EM
297 );
298
9043c4bf 299 $conn = $this->connect($db_config);
d7dea16c
EM
300 if (!$conn) {
301 return $results;
302 }
303
304 $innodb_support = FALSE;
fcf908c6 305 $result = mysqli_query($conn, "SHOW ENGINES");
306 while ($values = mysqli_fetch_array($result)) {
d7dea16c
EM
307 if ($values['Engine'] == 'InnoDB') {
308 if (strtolower($values['Support']) == 'yes' || strtolower($values['Support']) == 'default') {
309 $innodb_support = TRUE;
310 break;
311 }
312 }
313 }
314
315 if ($innodb_support) {
316 $results['severity'] = $this::REQUIREMENT_OK;
317 $results['details'] = 'MySQL supports InnoDB';
318 }
319 return $results;
320 }
321
322 /**
323 * @param array $db_config
324 *
325 * @return array
326 */
327 public function checkMysqlTempTables(array $db_config) {
328 $results = array(
329 'title' => 'CiviCRM MySQL Temp Tables',
330 'severity' => $this::REQUIREMENT_OK,
331 'details' => 'MySQL server supports temporary tables',
332 );
333
9043c4bf 334 $conn = $this->connect($db_config);
d7dea16c
EM
335 if (!$conn) {
336 $results['severity'] = $this::REQUIREMENT_ERROR;
337 $results['details'] = "Could not connect to database";
338 return $results;
339 }
340
fcf908c6 341 if (!@mysqli_select_db($conn, $db_config['database'])) {
d7dea16c
EM
342 $results['severity'] = $this::REQUIREMENT_ERROR;
343 $results['details'] = "Could not select the database";
344 return $results;
345 }
346
fcf908c6 347 $r = mysqli_query($conn, 'CREATE TEMPORARY TABLE civicrm_install_temp_table_test (test text)');
d7dea16c
EM
348 if (!$r) {
349 $results['severity'] = $this::REQUIREMENT_ERROR;
350 $results['details'] = "Database does not support creation of temporary tables";
351 return $results;
352 }
353
fcf908c6 354 mysqli_query($conn, 'DROP TEMPORARY TABLE civicrm_install_temp_table_test');
d7dea16c
EM
355 return $results;
356 }
357
358 /**
359 * @param $db_config
360 *
361 * @return array
362 */
363 public function checkMysqlTrigger($db_config) {
364 $results = array(
365 'title' => 'CiviCRM MySQL Trigger',
366 'severity' => $this::REQUIREMENT_OK,
367 'details' => 'Database supports MySQL triggers',
368 );
369
9043c4bf 370 $conn = $this->connect($db_config);
d7dea16c
EM
371 if (!$conn) {
372 $results['severity'] = $this::REQUIREMENT_ERROR;
373 $results['details'] = 'Could not connect to database';
374 return $results;
375 }
376
fcf908c6 377 if (!@mysqli_select_db($conn, $db_config['database'])) {
d7dea16c
EM
378 $results['severity'] = $this::REQUIREMENT_ERROR;
379 $results['details'] = "Could not select the database";
380 return $results;
381 }
382
fcf908c6 383 $r = mysqli_query($conn, 'CREATE TABLE civicrm_install_temp_table_test (test text)');
d7dea16c
EM
384 if (!$r) {
385 $results['severity'] = $this::REQUIREMENT_ERROR;
386 $results['details'] = 'Could not create a table to run test';
387 return $results;
388 }
389
fcf908c6 390 $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');
d7dea16c
EM
391 if (!$r) {
392 $results['severity'] = $this::REQUIREMENT_ERROR;
393 $results['details'] = 'Database does not support creation of triggers';
394 }
395 else {
fcf908c6 396 mysqli_query($conn, 'DROP TRIGGER civicrm_install_temp_table_test_trigger');
d7dea16c
EM
397 }
398
fcf908c6 399 mysqli_query($conn, 'DROP TABLE civicrm_install_temp_table_test');
d7dea16c
EM
400 return $results;
401 }
402
403 /**
404 * @param array $db_config
405 *
406 * @return array
407 */
408 public function checkMySQLAutoIncrementIncrementOne(array $db_config) {
409 $results = array(
410 'title' => 'CiviCRM MySQL AutoIncrementIncrement',
411 'severity' => $this::REQUIREMENT_OK,
412 'details' => 'MySQL server auto_increment_increment is 1',
413 );
414
9043c4bf 415 $conn = $this->connect($db_config);
d7dea16c
EM
416 if (!$conn) {
417 $results['severity'] = $this::REQUIREMENT_ERROR;
418 $results['details'] = 'Could not connect to database';
419 return $results;
420 }
421
fcf908c6 422 $r = mysqli_query($conn, "SHOW variables like 'auto_increment_increment'");
d7dea16c
EM
423 if (!$r) {
424 $results['severity'] = $this::REQUIREMENT_ERROR;
425 $results['details'] = 'Could not query database server variables';
426 return $results;
427 }
428
fcf908c6 429 $values = mysqli_fetch_row($r);
d7dea16c
EM
430 if ($values[1] != 1) {
431 $results['severity'] = $this::REQUIREMENT_ERROR;
432 $results['details'] = 'MySQL server auto_increment_increment is not 1';
433 }
434 return $results;
435 }
436
437 /**
438 * @param $db_config
439 *
440 * @return array
441 */
442 public function checkMysqlThreadStack($db_config) {
443 $min_thread_stack = 192;
444
445 $results = array(
446 'title' => 'CiviCRM Mysql thread stack',
447 'severity' => $this::REQUIREMENT_OK,
448 'details' => 'MySQL thread_stack is OK',
449 );
450
9043c4bf 451 $conn = $this->connect($db_config);
d7dea16c
EM
452 if (!$conn) {
453 $results['severity'] = $this::REQUIREMENT_ERROR;
454 $results['details'] = 'Could not connect to database';
455 return $results;
456 }
457
fcf908c6 458 if (!@mysqli_select_db($conn, $db_config['database'])) {
d7dea16c
EM
459 $results['severity'] = $this::REQUIREMENT_ERROR;
460 $results['details'] = 'Could not select the database';
461 return $results;
462 }
463
fcf908c6 464 $r = mysqli_query($conn, "SHOW VARIABLES LIKE 'thread_stack'"); // bytes => kb
d7dea16c
EM
465 if (!$r) {
466 $results['severity'] = $this::REQUIREMENT_ERROR;
467 $results['details'] = 'Could not query thread_stack value';
468 }
469 else {
fcf908c6 470 $values = mysqli_fetch_row($r);
d7dea16c
EM
471 if ($values[1] < (1024 * $min_thread_stack)) {
472 $results['severity'] = $this::REQUIREMENT_ERROR;
473 $results['details'] = 'MySQL thread_stack is ' . ($values[1] / 1024) . "kb (minimum required is {$min_thread_stack} kb";
474 }
475 }
476
477 return $results;
478 }
479
480 /**
481 * @param $db_config
482 *
483 * @return array
484 */
00be9182 485 public function checkMysqlLockTables($db_config) {
d7dea16c
EM
486 $results = array(
487 'title' => 'CiviCRM MySQL Lock Tables',
488 'severity' => $this::REQUIREMENT_OK,
489 'details' => 'Can successfully lock and unlock tables',
490 );
491
9043c4bf 492 $conn = $this->connect($db_config);
d7dea16c
EM
493 if (!$conn) {
494 $results['severity'] = $this::REQUIREMENT_ERROR;
46bcf597 495 $results['details'] = 'Could not connect to database';
d7dea16c
EM
496 return $results;
497 }
498
fcf908c6 499 if (!@mysqli_select_db($conn, $db_config['database'])) {
d7dea16c
EM
500 $results['severity'] = $this::REQUIREMENT_ERROR;
501 $results['details'] = 'Could not select the database';
fcf908c6 502 mysqli_close($conn);
d7dea16c
EM
503 return $results;
504 }
505
fcf908c6 506 $r = mysqli_query($conn, 'CREATE TEMPORARY TABLE civicrm_install_temp_table_test (test text)');
d7dea16c
EM
507 if (!$r) {
508 $results['severity'] = $this::REQUIREMENT_ERROR;
509 $results['details'] = 'Could not create a table';
fcf908c6 510 mysqli_close($conn);
d7dea16c
EM
511 return $results;
512 }
513
fcf908c6 514 $r = mysqli_query($conn, 'LOCK TABLES civicrm_install_temp_table_test WRITE');
d7dea16c
EM
515 if (!$r) {
516 $results['severity'] = $this::REQUIREMENT_ERROR;
517 $results['details'] = 'Could not obtain a write lock';
fcf908c6 518 mysqli_close($conn);
d7dea16c
EM
519 return $results;
520 }
521
fcf908c6 522 $r = mysqli_query($conn, 'UNLOCK TABLES');
d7dea16c
EM
523 if (!$r) {
524 $results['severity'] = $this::REQUIREMENT_ERROR;
525 $results['details'] = 'Could not release table lock';
526 }
527
fcf908c6 528 mysqli_close($conn);
d7dea16c
EM
529 return $results;
530 }
531
532 /**
533 * @param $file_paths
534 *
535 * @return array
536 */
537 public function checkFilepathIsWritable($file_paths) {
538 $results = array(
539 'title' => 'CiviCRM directories are writable',
540 'severity' => $this::REQUIREMENT_OK,
541 'details' => 'All required directories are writable: ' . implode(', ', $file_paths),
542 );
543
544 $unwritable_dirs = array();
545 foreach ($file_paths as $path) {
546 if (!is_writable($path)) {
547 $unwritable_dirs[] = $path;
548 }
549 }
550
551 if ($unwritable_dirs) {
552 $results['severity'] = $this::REQUIREMENT_ERROR;
553 $results['details'] = "The following directories need to be made writable by the webserver: " . implode(', ', $unwritable_dirs);
554 }
555
556 return $results;
557 }
96025800 558
d7dea16c 559}