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