INFRA-132 - Fix spacing of @return tag in comments
[civicrm-core.git] / Civi / Install / Requirements.php
1 <?php
2
3 namespace Civi\Install;
4
5 /**
6 * Class Requirements
7 * @package Civi\Install
8 */
9 class 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 *
52 * @return array
53 * An array of check summaries. Each array contains the keys 'title', 'severity', and 'details'.
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)
88 * = database (name of database to select)
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
104 /**
105 * Check configured php Memory
106 * @return array
107 */
108 public function checkMemory() {
109 $min = 1024 * 1024 * 32;
110 $recommended = 1024 * 1024 * 64;
111
112 $mem = $this->getPHPMemory();
113 $mem_string = ini_get('memory_limit');
114
115 $results = array(
116 'title' => 'CiviCRM memory check',
117 'severity' => $this::REQUIREMENT_OK,
118 'details' => "You have $mem_string allocated (minimum 32Mb, recommended 64Mb)",
119 );
120
121 if ($mem < $min && $mem > 0) {
122 $results['severity'] = $this::REQUIREMENT_ERROR;
123 }
124 elseif ($mem < $recommended && $mem != 0) {
125 $results['severity'] = $this::REQUIREMENT_WARNING;
126 }
127 elseif ($mem == 0) {
128 $results['details'] = "Cannot determine PHP memory allocation. Install only if you're sure you've allocated at least 32 MB.";
129 $results['severity'] = $this::REQUIREMENT_WARNING;
130 }
131
132 return $results;
133 }
134
135 /**
136 * Get Configured PHP memory
137 * @return float
138 */
139 protected function getPHPMemory() {
140 $memString = ini_get("memory_limit");
141
142 switch (strtolower(substr($memString, -1))) {
143 case "k":
144 return round(substr($memString, 0, -1) * 1024);
145 case "m":
146 return round(substr($memString, 0, -1) * 1024 * 1024);
147 case "g":
148 return round(substr($memString, 0, -1) * 1024 * 1024 * 1024);
149 default:
150 return round($memString);
151 }
152 }
153
154 /**
155 * @return array
156 */
157 public function checkServerVariables() {
158 $results = array(
159 'title' => 'CiviCRM PHP server variables',
160 'severity' => $this::REQUIREMENT_OK,
161 'details' => 'The required $_SERVER variables are set',
162 );
163
164 $required_variables = array('SCRIPT_NAME', 'HTTP_HOST', 'SCRIPT_FILENAME');
165 $missing = array();
166
167 foreach ($required_variables as $required_variable) {
168 if (empty($_SERVER[$required_variable])) {
169 $missing[] = '$_SERVER[' . $required_variable . ']';
170 }
171 }
172
173 if ($missing) {
174 $results['severity'] = $this::REQUIREMENT_ERROR;
175 $results['details'] = 'The following PHP variables are not set: ' . implode(', ', $missing);
176 }
177
178 return $results;
179 }
180
181 /**
182 * @return array
183 */
184 public function checkJsonEncodeExists() {
185 $results = array(
186 'title' => 'CiviCRM JSON encoding support',
187 'severity' => $this::REQUIREMENT_OK,
188 'details' => 'Function json_encode() found',
189 );
190 if (!function_exists('json_encode')) {
191 $results['severity'] = $this::REQUIREMENT_ERROR;
192 $results['details'] = 'Function json_encode() does not exist';
193 }
194
195 return $results;
196 }
197
198 /**
199 * @return array
200 */
201 public function checkMysqlConnectExists() {
202 $results = array(
203 'title' => 'CiviCRM MySQL check',
204 'severity' => $this::REQUIREMENT_OK,
205 'details' => 'Function mysql_connect() found',
206 );
207 if (!function_exists('mysql_connect')) {
208 $results['severity'] = $this::REQUIREMENT_ERROR;
209 $results['details'] = 'Function mysql_connect() does not exist';
210 }
211
212 return $results;
213 }
214
215 /**
216 * @param array $db_config
217 *
218 * @return array
219 */
220 public function checkMysqlConnection(array $db_config) {
221 $results = array(
222 'title' => 'CiviCRM MySQL connection',
223 'severity' => $this::REQUIREMENT_OK,
224 'details' => "Connected",
225 );
226
227 $conn = @mysql_connect($db_config['host'], $db_config['username'], $db_config['password']);
228
229 if (!$conn) {
230 $results['details'] = mysql_error();
231 $results['severity'] = $this::REQUIREMENT_ERROR;
232 return $results;
233 }
234
235 if (!@mysql_select_db($db_config['database'], $conn)) {
236 $results['details'] = mysql_error();
237 $results['severity'] = $this::REQUIREMENT_ERROR;
238 return $results;
239 }
240
241 return $results;
242 }
243
244 /**
245 * @param array $db_config
246 *
247 * @return array
248 */
249 public function checkMysqlVersion(array $db_config) {
250 $min = '5.1';
251 $results = array(
252 'title' => 'CiviCRM MySQL Version',
253 'severity' => $this::REQUIREMENT_OK,
254 );
255
256 $conn = @mysql_connect($db_config['host'], $db_config['username'], $db_config['password']);
257 if (!$conn || !($info = mysql_get_server_info($conn))) {
258 $results['severity'] = $this::REQUIREMENT_WARNING;
259 $results['details'] = "Cannot determine the version of MySQL installed. Please ensure at least version {$min} is installed.";
260 return $results;
261 }
262
263 if (version_compare($info, $min) == -1) {
264 $results['severity'] = $this::REQUIREMENT_ERROR;
265 $results['details'] = "MySQL version is {$info}; minimum required is {$min}";
266 return $results;
267 }
268
269 $results['details'] = "MySQL version is {$info}";
270 return $results;
271 }
272
273 /**
274 * @param array $db_config
275 *
276 * @return array
277 */
278 public function checkMysqlInnodb(array $db_config) {
279 $results = array(
280 'title' => 'CiviCRM InnoDB support',
281 'severity' => $this::REQUIREMENT_ERROR,
282 'details' => 'Could not determine if MySQL has InnoDB support. Assuming none.'
283 );
284
285 $conn = @mysql_connect($db_config['host'], $db_config['username'], $db_config['password']);
286 if (!$conn) {
287 return $results;
288 }
289
290 $innodb_support = FALSE;
291 $result = mysql_query("SHOW ENGINES", $conn);
292 while ($values = mysql_fetch_array($result)) {
293 if ($values['Engine'] == 'InnoDB') {
294 if (strtolower($values['Support']) == 'yes' || strtolower($values['Support']) == 'default') {
295 $innodb_support = TRUE;
296 break;
297 }
298 }
299 }
300
301 if ($innodb_support) {
302 $results['severity'] = $this::REQUIREMENT_OK;
303 $results['details'] = 'MySQL supports InnoDB';
304 }
305 return $results;
306 }
307
308 /**
309 * @param array $db_config
310 *
311 * @return array
312 */
313 public function checkMysqlTempTables(array $db_config) {
314 $results = array(
315 'title' => 'CiviCRM MySQL Temp Tables',
316 'severity' => $this::REQUIREMENT_OK,
317 'details' => 'MySQL server supports temporary tables',
318 );
319
320 $conn = @mysql_connect($db_config['host'], $db_config['username'], $db_config['password']);
321 if (!$conn) {
322 $results['severity'] = $this::REQUIREMENT_ERROR;
323 $results['details'] = "Could not connect to database";
324 return $results;
325 }
326
327 if (!@mysql_select_db($db_config['database'], $conn)) {
328 $results['severity'] = $this::REQUIREMENT_ERROR;
329 $results['details'] = "Could not select the database";
330 return $results;
331 }
332
333 $r = mysql_query('CREATE TEMPORARY TABLE civicrm_install_temp_table_test (test text)', $conn);
334 if (!$r) {
335 $results['severity'] = $this::REQUIREMENT_ERROR;
336 $results['details'] = "Database does not support creation of temporary tables";
337 return $results;
338 }
339
340 mysql_query('DROP TEMPORARY TABLE civicrm_install_temp_table_test');
341 return $results;
342 }
343
344 /**
345 * @param $db_config
346 *
347 * @return array
348 */
349 public function checkMysqlTrigger($db_config) {
350 $results = array(
351 'title' => 'CiviCRM MySQL Trigger',
352 'severity' => $this::REQUIREMENT_OK,
353 'details' => 'Database supports MySQL triggers',
354 );
355
356 $conn = @mysql_connect($db_config['host'], $db_config['username'], $db_config['password']);
357 if (!$conn) {
358 $results['severity'] = $this::REQUIREMENT_ERROR;
359 $results['details'] = 'Could not connect to database';
360 return $results;
361 }
362
363 if (!@mysql_select_db($db_config['database'], $conn)) {
364 $results['severity'] = $this::REQUIREMENT_ERROR;
365 $results['details'] = "Could not select the database";
366 return $results;
367 }
368
369 $r = mysql_query('CREATE TABLE civicrm_install_temp_table_test (test text)', $conn);
370 if (!$r) {
371 $results['severity'] = $this::REQUIREMENT_ERROR;
372 $results['details'] = 'Could not create a table to run test';
373 return $results;
374 }
375
376 $r = mysql_query('CREATE TRIGGER civicrm_install_temp_table_test_trigger BEFORE INSERT ON civicrm_install_temp_table_test FOR EACH ROW BEGIN END');
377 if (!$r) {
378 $results['severity'] = $this::REQUIREMENT_ERROR;
379 $results['details'] = 'Database does not support creation of triggers';
380 }
381 else {
382 mysql_query('DROP TRIGGER civicrm_install_temp_table_test_trigger');
383 }
384
385 mysql_query('DROP TABLE civicrm_install_temp_table_test');
386 return $results;
387 }
388
389 /**
390 * @param array $db_config
391 *
392 * @return array
393 */
394 public function checkMySQLAutoIncrementIncrementOne(array $db_config) {
395 $results = array(
396 'title' => 'CiviCRM MySQL AutoIncrementIncrement',
397 'severity' => $this::REQUIREMENT_OK,
398 'details' => 'MySQL server auto_increment_increment is 1',
399 );
400
401 $conn = @mysql_connect($db_config['host'], $db_config['username'], $db_config['password']);
402 if (!$conn) {
403 $results['severity'] = $this::REQUIREMENT_ERROR;
404 $results['details'] = 'Could not connect to database';
405 return $results;
406 }
407
408 $r = mysql_query("SHOW variables like 'auto_increment_increment'", $conn);
409 if (!$r) {
410 $results['severity'] = $this::REQUIREMENT_ERROR;
411 $results['details'] = 'Could not query database server variables';
412 return $results;
413 }
414
415 $values = mysql_fetch_row($r);
416 if ($values[1] != 1) {
417 $results['severity'] = $this::REQUIREMENT_ERROR;
418 $results['details'] = 'MySQL server auto_increment_increment is not 1';
419 }
420 return $results;
421 }
422
423 /**
424 * @param $db_config
425 *
426 * @return array
427 */
428 public function checkMysqlThreadStack($db_config) {
429 $min_thread_stack = 192;
430
431 $results = array(
432 'title' => 'CiviCRM Mysql thread stack',
433 'severity' => $this::REQUIREMENT_OK,
434 'details' => 'MySQL thread_stack is OK',
435 );
436
437 $conn = @mysql_connect($db_config['server'], $db_config['username'], $db_config['password']);
438 if (!$conn) {
439 $results['severity'] = $this::REQUIREMENT_ERROR;
440 $results['details'] = 'Could not connect to database';
441 return $results;
442 }
443
444 if (!@mysql_select_db($db_config['database'], $conn)) {
445 $results['severity'] = $this::REQUIREMENT_ERROR;
446 $results['details'] = 'Could not select the database';
447 return $results;
448 }
449
450 $r = mysql_query("SHOW VARIABLES LIKE 'thread_stack'", $conn); // bytes => kb
451 if (!$r) {
452 $results['severity'] = $this::REQUIREMENT_ERROR;
453 $results['details'] = 'Could not query thread_stack value';
454 }
455 else {
456 $values = mysql_fetch_row($r);
457 if ($values[1] < (1024 * $min_thread_stack)) {
458 $results['severity'] = $this::REQUIREMENT_ERROR;
459 $results['details'] = 'MySQL thread_stack is ' . ($values[1] / 1024) . "kb (minimum required is {$min_thread_stack} kb";
460 }
461 }
462
463 return $results;
464 }
465
466 /**
467 * @param $db_config
468 *
469 * @return array
470 */
471 public function checkMysqlLockTables($db_config) {
472 $results = array(
473 'title' => 'CiviCRM MySQL Lock Tables',
474 'severity' => $this::REQUIREMENT_OK,
475 'details' => 'Can successfully lock and unlock tables',
476 );
477
478 $conn = @mysql_connect($db_config['server'], $db_config['username'], $db_config['password']);
479 if (!$conn) {
480 $results['severity'] = $this::REQUIREMENT_ERROR;
481 $results['details'] = 'Could not connect to database';
482 return $results;
483 }
484
485 if (!@mysql_select_db($db_config['database'], $conn)) {
486 $results['severity'] = $this::REQUIREMENT_ERROR;
487 $results['details'] = 'Could not select the database';
488 mysql_close($conn);
489 return $results;
490 }
491
492 $r = mysql_query('CREATE TEMPORARY TABLE civicrm_install_temp_table_test (test text)', $conn);
493 if (!$r) {
494 $results['severity'] = $this::REQUIREMENT_ERROR;
495 $results['details'] = 'Could not create a table';
496 mysql_close($conn);
497 return $results;
498 }
499
500 $r = mysql_query('LOCK TABLES civicrm_install_temp_table_test WRITE', $conn);
501 if (!$r) {
502 $results['severity'] = $this::REQUIREMENT_ERROR;
503 $results['details'] = 'Could not obtain a write lock';
504 mysql_close($conn);
505 return $results;
506 }
507
508 $r = mysql_query('UNLOCK TABLES', $conn);
509 if (!$r) {
510 $results['severity'] = $this::REQUIREMENT_ERROR;
511 $results['details'] = 'Could not release table lock';
512 }
513
514 mysql_close($conn);
515 return $results;
516 }
517
518 /**
519 * @param $file_paths
520 *
521 * @return array
522 */
523 public function checkFilepathIsWritable($file_paths) {
524 $results = array(
525 'title' => 'CiviCRM directories are writable',
526 'severity' => $this::REQUIREMENT_OK,
527 'details' => 'All required directories are writable: ' . implode(', ', $file_paths),
528 );
529
530 $unwritable_dirs = array();
531 foreach ($file_paths as $path) {
532 if (!is_writable($path)) {
533 $unwritable_dirs[] = $path;
534 }
535 }
536
537 if ($unwritable_dirs) {
538 $results['severity'] = $this::REQUIREMENT_ERROR;
539 $results['details'] = "The following directories need to be made writable by the webserver: " . implode(', ', $unwritable_dirs);
540 }
541
542 return $results;
543 }
544 }