Merge pull request #15248 from MegaphoneJon/reporting-19
[civicrm-core.git] / CRM / Utils / Check / Component / Env.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2020 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2020
32 */
33 class CRM_Utils_Check_Component_Env extends CRM_Utils_Check_Component {
34
35 /**
36 * @return array
37 */
38 public function checkPhpVersion() {
39 $messages = [];
40 $phpVersion = phpversion();
41
42 if (version_compare($phpVersion, CRM_Upgrade_Incremental_General::RECOMMENDED_PHP_VER) >= 0) {
43 $messages[] = new CRM_Utils_Check_Message(
44 __FUNCTION__,
45 ts('This system uses PHP version %1 which meets or exceeds the recommendation of %2.',
46 [
47 1 => $phpVersion,
48 2 => CRM_Upgrade_Incremental_General::RECOMMENDED_PHP_VER,
49 ]),
50 ts('PHP Up-to-Date'),
51 \Psr\Log\LogLevel::INFO,
52 'fa-server'
53 );
54 }
55 elseif (version_compare($phpVersion, CRM_Upgrade_Incremental_General::MIN_RECOMMENDED_PHP_VER) >= 0) {
56 $messages[] = new CRM_Utils_Check_Message(
57 __FUNCTION__,
58 ts('This system uses PHP version %1. This meets the minimum recommendations and you do not need to upgrade immediately, but the preferred version is %2.',
59 [
60 1 => $phpVersion,
61 2 => CRM_Upgrade_Incremental_General::RECOMMENDED_PHP_VER,
62 ]),
63 ts('PHP Out-of-Date'),
64 \Psr\Log\LogLevel::NOTICE,
65 'fa-server'
66 );
67 }
68 elseif (version_compare($phpVersion, CRM_Upgrade_Incremental_General::MIN_INSTALL_PHP_VER) >= 0) {
69 $messages[] = new CRM_Utils_Check_Message(
70 __FUNCTION__,
71 ts('This system uses PHP version %1. This meets the minimum requirements for CiviCRM to function but is not recommended. At least PHP version %2 is recommended; the preferred version is %3.',
72 [
73 1 => $phpVersion,
74 2 => CRM_Upgrade_Incremental_General::MIN_RECOMMENDED_PHP_VER,
75 3 => CRM_Upgrade_Incremental_General::RECOMMENDED_PHP_VER,
76 ]),
77 ts('PHP Out-of-Date'),
78 \Psr\Log\LogLevel::WARNING,
79 'fa-server'
80 );
81 }
82 else {
83 $messages[] = new CRM_Utils_Check_Message(
84 __FUNCTION__,
85 ts('This system uses PHP version %1. To ensure the continued operation of CiviCRM, upgrade your server now. At least PHP version %2 is recommended; the preferrred version is %3.',
86 [
87 1 => $phpVersion,
88 2 => CRM_Upgrade_Incremental_General::MIN_RECOMMENDED_PHP_VER,
89 3 => CRM_Upgrade_Incremental_General::RECOMMENDED_PHP_VER,
90 ]),
91 ts('PHP Out-of-Date'),
92 \Psr\Log\LogLevel::ERROR,
93 'fa-server'
94 );
95 }
96
97 return $messages;
98 }
99
100 /**
101 * @return array
102 */
103 public function checkPhpMysqli() {
104 $messages = [];
105
106 if (!extension_loaded('mysqli')) {
107 $messages[] = new CRM_Utils_Check_Message(
108 __FUNCTION__,
109 ts('Future versions of CiviCRM may require the PHP extension "%2". To ensure that your system will be compatible, please install it in advance. For more explanation, see <a href="%1">the announcement</a>.',
110 [
111 1 => 'https://civicrm.org/blog/totten/psa-please-verify-php-extension-mysqli',
112 2 => 'mysqli',
113 ]),
114 ts('Forward Compatibility: Enable "mysqli"'),
115 \Psr\Log\LogLevel::WARNING,
116 'fa-server'
117 );
118 }
119
120 return $messages;
121 }
122
123 /**
124 * Check that the MySQL time settings match the PHP time settings.
125 *
126 * @return array<CRM_Utils_Check_Message> an empty array, or a list of warnings
127 */
128 public function checkMysqlTime() {
129 $messages = [];
130
131 $phpNow = date('Y-m-d H:i');
132 $sqlNow = CRM_Core_DAO::singleValueQuery("SELECT date_format(now(), '%Y-%m-%d %H:%i')");
133 if (!CRM_Utils_Time::isEqual($phpNow, $sqlNow, 2.5 * 60)) {
134 $messages[] = new CRM_Utils_Check_Message(
135 __FUNCTION__,
136 ts('Timestamps reported by MySQL (eg "%2") and PHP (eg "%3" ) are mismatched.<br /><a href="%1">Read more about this warning</a>', [
137 1 => CRM_Utils_System::getWikiBaseURL() . 'checkMysqlTime',
138 2 => $sqlNow,
139 3 => $phpNow,
140 ]),
141 ts('Timestamp Mismatch'),
142 \Psr\Log\LogLevel::ERROR,
143 'fa-server'
144 );
145 }
146
147 return $messages;
148 }
149
150 /**
151 * @return array
152 */
153 public function checkDebug() {
154 $config = CRM_Core_Config::singleton();
155 if ($config->debug) {
156 $message = new CRM_Utils_Check_Message(
157 __FUNCTION__,
158 ts('Warning: Debug is enabled in <a href="%1">system settings</a>. This should not be enabled on production servers.',
159 [1 => CRM_Utils_System::url('civicrm/admin/setting/debug', 'reset=1')]),
160 ts('Debug Mode Enabled'),
161 \Psr\Log\LogLevel::WARNING,
162 'fa-bug'
163 );
164 $message->addAction(
165 ts('Disable Debug Mode'),
166 ts('Disable debug mode now?'),
167 'api3',
168 ['Setting', 'create', ['debug_enabled' => 0]]
169 );
170 return [$message];
171 }
172
173 return [];
174 }
175
176 /**
177 * @return array
178 */
179 public function checkOutboundMail() {
180 $messages = [];
181
182 $mailingInfo = Civi::settings()->get('mailing_backend');
183 if (($mailingInfo['outBound_option'] == CRM_Mailing_Config::OUTBOUND_OPTION_REDIRECT_TO_DB
184 || (defined('CIVICRM_MAIL_LOG') && CIVICRM_MAIL_LOG)
185 || $mailingInfo['outBound_option'] == CRM_Mailing_Config::OUTBOUND_OPTION_DISABLED
186 || $mailingInfo['outBound_option'] == CRM_Mailing_Config::OUTBOUND_OPTION_MOCK)
187 ) {
188 $messages[] = new CRM_Utils_Check_Message(
189 __FUNCTION__,
190 ts('Warning: Outbound email is disabled in <a href="%1">system settings</a>. Proper settings should be enabled on production servers.',
191 [1 => CRM_Utils_System::url('civicrm/admin/setting/smtp', 'reset=1')]),
192 ts('Outbound Email Disabled'),
193 \Psr\Log\LogLevel::WARNING,
194 'fa-envelope'
195 );
196 }
197
198 return $messages;
199 }
200
201 /**
202 * Check that domain email and org name are set
203 * @return array
204 */
205 public function checkDomainNameEmail() {
206 $messages = [];
207
208 list($domainEmailName, $domainEmailAddress) = CRM_Core_BAO_Domain::getNameAndEmail(TRUE);
209 $domain = CRM_Core_BAO_Domain::getDomain();
210 $domainName = $domain->name;
211 $fixEmailUrl = CRM_Utils_System::url("civicrm/admin/options/from_email_address", "&reset=1");
212 $fixDomainName = CRM_Utils_System::url("civicrm/admin/domain", "action=update&reset=1");
213
214 if (!$domainEmailAddress || $domainEmailAddress == 'info@EXAMPLE.ORG') {
215 if (!$domainName || $domainName == 'Default Domain Name') {
216 $msg = ts("Please enter your organization's <a href=\"%1\">name, primary address </a> and <a href=\"%2\">default FROM Email Address </a> (for system-generated emails).",
217 [
218 1 => $fixDomainName,
219 2 => $fixEmailUrl,
220 ]
221 );
222 }
223 else {
224 $msg = ts('Please enter a <a href="%1">default FROM Email Address</a> (for system-generated emails).',
225 [1 => $fixEmailUrl]);
226 }
227 }
228 elseif (!$domainName || $domainName == 'Default Domain Name') {
229 $msg = ts("Please enter your organization's <a href=\"%1\">name and primary address</a>.",
230 [1 => $fixDomainName]);
231 }
232
233 if (!empty($msg)) {
234 $messages[] = new CRM_Utils_Check_Message(
235 __FUNCTION__,
236 $msg,
237 ts('Complete Setup'),
238 \Psr\Log\LogLevel::WARNING,
239 'fa-check-square-o'
240 );
241 }
242
243 return $messages;
244 }
245
246 /**
247 * Checks if a default bounce handling mailbox is set up
248 * @return array
249 */
250 public function checkDefaultMailbox() {
251 $messages = [];
252 $config = CRM_Core_Config::singleton();
253
254 if (in_array('CiviMail', $config->enableComponents) &&
255 CRM_Core_BAO_MailSettings::defaultDomain() == "EXAMPLE.ORG"
256 ) {
257 $message = new CRM_Utils_Check_Message(
258 __FUNCTION__,
259 ts('Please configure a <a href="%1">default mailbox</a> for CiviMail.',
260 [1 => CRM_Utils_System::url('civicrm/admin/mailSettings', "reset=1")]),
261 ts('Configure Default Mailbox'),
262 \Psr\Log\LogLevel::WARNING,
263 'fa-envelope'
264 );
265 $docUrl = 'target="_blank" href="' . CRM_Utils_System::docURL(['page' => 'user/advanced-configuration/email-system-configuration/', 'URLonly' => TRUE]) . '""';
266 $message->addHelp(
267 ts('A default mailbox must be configured for email bounce processing.') . '<br />' .
268 ts("Learn more in the <a %1>online documentation</a>.", [1 => $docUrl])
269 );
270 $messages[] = $message;
271 }
272
273 return $messages;
274 }
275
276 /**
277 * Checks if cron has run in a reasonable amount of time
278 * @return array
279 */
280 public function checkLastCron() {
281 $messages = [];
282
283 $statusPreference = new CRM_Core_DAO_StatusPreference();
284 $statusPreference->domain_id = CRM_Core_Config::domainID();
285 $statusPreference->name = 'checkLastCron';
286
287 if ($statusPreference->find(TRUE) && !empty($statusPreference->check_info)) {
288 $lastCron = $statusPreference->check_info;
289 $msg = ts('Last cron run at %1.', [1 => CRM_Utils_Date::customFormat(date('c', $lastCron))]);
290 }
291 else {
292 $lastCron = 0;
293 $msg = ts('No cron runs have been recorded.');
294 }
295
296 if ($lastCron > gmdate('U') - 3600) {
297 $messages[] = new CRM_Utils_Check_Message(
298 __FUNCTION__,
299 $msg,
300 ts('Cron Running OK'),
301 \Psr\Log\LogLevel::INFO,
302 'fa-clock-o'
303 );
304 }
305 else {
306 $cronLink = 'target="_blank" href="' . htmlentities(CRM_Utils_System::docURL2('sysadmin/setup/jobs/', TRUE)) . '""';
307 $msg .= '<p>' . ts('To enable scheduling support, please <a %1>set up the cron job</a>.', [
308 1 => $cronLink,
309 ]) . '</p>';
310 $message = new CRM_Utils_Check_Message(
311 __FUNCTION__,
312 $msg,
313 ts('Cron Not Running'),
314 ($lastCron > gmdate('U') - 86400) ? \Psr\Log\LogLevel::WARNING : \Psr\Log\LogLevel::ERROR,
315 'fa-clock-o'
316 );
317 $messages[] = $message;
318 }
319
320 return $messages;
321 }
322
323 /**
324 * Recommend that sites use path-variables for their directories and URLs.
325 * @return array
326 */
327 public function checkUrlVariables() {
328 $messages = [];
329 $hasOldStyle = FALSE;
330 $settingNames = [
331 'userFrameworkResourceURL',
332 'imageUploadURL',
333 'customCSSURL',
334 'extensionsURL',
335 ];
336
337 foreach ($settingNames as $settingName) {
338 $settingValue = Civi::settings()->get($settingName);
339 if (!empty($settingValue) && $settingValue{0} != '[') {
340 $hasOldStyle = TRUE;
341 break;
342 }
343 }
344
345 if ($hasOldStyle) {
346 $message = new CRM_Utils_Check_Message(
347 __FUNCTION__,
348 ts('<a href="%1">Resource URLs</a> may use absolute paths, relative paths, or variables. Absolute paths are more difficult to maintain. To maximize portability, consider using a variable in each URL (eg "<tt>[cms.root]</tt>" or "<tt>[civicrm.files]</tt>").',
349 [1 => CRM_Utils_System::url('civicrm/admin/setting/url', "reset=1")]),
350 ts('Resource URLs: Make them portable'),
351 \Psr\Log\LogLevel::NOTICE,
352 'fa-server'
353 );
354 $messages[] = $message;
355 }
356
357 return $messages;
358 }
359
360 /**
361 * Recommend that sites use path-variables for their directories and URLs.
362 * @return array
363 */
364 public function checkDirVariables() {
365 $messages = [];
366 $hasOldStyle = FALSE;
367 $settingNames = [
368 'uploadDir',
369 'imageUploadDir',
370 'customFileUploadDir',
371 'customTemplateDir',
372 'customPHPPathDir',
373 'extensionsDir',
374 ];
375
376 foreach ($settingNames as $settingName) {
377 $settingValue = Civi::settings()->get($settingName);
378 if (!empty($settingValue) && $settingValue{0} != '[') {
379 $hasOldStyle = TRUE;
380 break;
381 }
382 }
383
384 if ($hasOldStyle) {
385 $message = new CRM_Utils_Check_Message(
386 __FUNCTION__,
387 ts('<a href="%1">Directories</a> may use absolute paths, relative paths, or variables. Absolute paths are more difficult to maintain. To maximize portability, consider using a variable in each directory (eg "<tt>[cms.root]</tt>" or "<tt>[civicrm.files]</tt>").',
388 [1 => CRM_Utils_System::url('civicrm/admin/setting/path', "reset=1")]),
389 ts('Directory Paths: Make them portable'),
390 \Psr\Log\LogLevel::NOTICE,
391 'fa-server'
392 );
393 $messages[] = $message;
394 }
395
396 return $messages;
397 }
398
399 /**
400 * Check that important directories are writable.
401 *
402 * @return array
403 * Any CRM_Utils_Check_Message instances that need to be generated.
404 */
405 public function checkDirsWritable() {
406 $notWritable = [];
407
408 $config = CRM_Core_Config::singleton();
409 $directories = [
410 'uploadDir' => ts('Temporary Files Directory'),
411 'imageUploadDir' => ts('Images Directory'),
412 'customFileUploadDir' => ts('Custom Files Directory'),
413 ];
414
415 foreach ($directories as $directory => $label) {
416 $file = CRM_Utils_File::createFakeFile($config->$directory);
417
418 if ($file === FALSE) {
419 $notWritable[] = "$label ({$config->$directory})";
420 }
421 else {
422 $dirWithSlash = CRM_Utils_File::addTrailingSlash($config->$directory);
423 unlink($dirWithSlash . $file);
424 }
425 }
426
427 $messages = [];
428
429 if (!empty($notWritable)) {
430 $messages[] = new CRM_Utils_Check_Message(
431 __FUNCTION__,
432 ts('The %1 is not writable. Please check your file permissions.', [
433 1 => implode(', ', $notWritable),
434 'count' => count($notWritable),
435 'plural' => 'The following directories are not writable: %1. Please check your file permissions.',
436 ]),
437 ts('Directory not writable', [
438 'count' => count($notWritable),
439 'plural' => 'Directories not writable',
440 ]),
441 \Psr\Log\LogLevel::ERROR,
442 'fa-ban'
443 );
444 }
445
446 return $messages;
447 }
448
449 /**
450 * Checks if new versions are available
451 * @return array
452 */
453 public function checkVersion() {
454 $messages = [];
455 try {
456 $vc = new CRM_Utils_VersionCheck();
457 $vc->initialize();
458 }
459 catch (Exception $e) {
460 $messages[] = new CRM_Utils_Check_Message(
461 'checkVersionError',
462 ts('Directory %1 is not writable. Please change your file permissions.',
463 [1 => dirname($vc->cacheFile)]),
464 ts('Directory not writable'),
465 \Psr\Log\LogLevel::ERROR,
466 'fa-times-circle-o'
467 );
468 return $messages;
469 }
470
471 // Show a notice if the version_check job is disabled
472 if (empty($vc->cronJob['is_active'])) {
473 $args = empty($vc->cronJob['id']) ? ['reset' => 1] : ['reset' => 1, 'action' => 'update', 'id' => $vc->cronJob['id']];
474 $messages[] = new CRM_Utils_Check_Message(
475 'checkVersionDisabled',
476 ts('The check for new versions of CiviCRM has been disabled. <a %1>Re-enable the scheduled job</a> to receive important security update notifications.', [1 => 'href="' . CRM_Utils_System::url('civicrm/admin/job', $args) . '"']),
477 ts('Update Check Disabled'),
478 \Psr\Log\LogLevel::NOTICE,
479 'fa-times-circle-o'
480 );
481 }
482
483 if ($vc->isInfoAvailable) {
484 $severities = [
485 'info' => CRM_Utils_Check::severityMap(\Psr\Log\LogLevel::INFO),
486 'notice' => CRM_Utils_Check::severityMap(\Psr\Log\LogLevel::NOTICE) ,
487 'warning' => CRM_Utils_Check::severityMap(\Psr\Log\LogLevel::WARNING) ,
488 'critical' => CRM_Utils_Check::severityMap(\Psr\Log\LogLevel::CRITICAL),
489 ];
490 foreach ($vc->getVersionMessages() as $msg) {
491 $messages[] = new CRM_Utils_Check_Message(__FUNCTION__ . '_' . $msg['name'],
492 $msg['message'], $msg['title'], $severities[$msg['severity']], 'fa-cloud-upload');
493 }
494 }
495
496 return $messages;
497 }
498
499 /**
500 * Checks if extensions are set up properly
501 * @return array
502 */
503 public function checkExtensions() {
504 $messages = [];
505 $extensionSystem = CRM_Extension_System::singleton();
506 $mapper = $extensionSystem->getMapper();
507 $manager = $extensionSystem->getManager();
508
509 if ($extensionSystem->getDefaultContainer()) {
510 $basedir = $extensionSystem->getDefaultContainer()->baseDir;
511 }
512
513 if (empty($basedir)) {
514 // no extension directory
515 $messages[] = new CRM_Utils_Check_Message(
516 __FUNCTION__,
517 ts('Your extensions directory is not set. Click <a href="%1">here</a> to set the extensions directory.',
518 [1 => CRM_Utils_System::url('civicrm/admin/setting/path', 'reset=1')]),
519 ts('Directory not writable'),
520 \Psr\Log\LogLevel::NOTICE,
521 'fa-plug'
522 );
523 return $messages;
524 }
525
526 if (!is_dir($basedir)) {
527 $messages[] = new CRM_Utils_Check_Message(
528 __FUNCTION__,
529 ts('Your extensions directory path points to %1, which is not a directory. Please check your file system.',
530 [1 => $basedir]),
531 ts('Extensions directory incorrect'),
532 \Psr\Log\LogLevel::ERROR,
533 'fa-plug'
534 );
535 return $messages;
536 }
537 elseif (!is_writable($basedir)) {
538 $messages[] = new CRM_Utils_Check_Message(
539 __FUNCTION__ . 'Writable',
540 ts('Your extensions directory (%1) is read-only. If you would like to perform downloads or upgrades, then change the file permissions.',
541 [1 => $basedir]),
542 ts('Read-Only Extensions'),
543 \Psr\Log\LogLevel::NOTICE,
544 'fa-plug'
545 );
546 }
547
548 if (empty($extensionSystem->getDefaultContainer()->baseUrl)) {
549 $messages[] = new CRM_Utils_Check_Message(
550 __FUNCTION__ . 'URL',
551 ts('The extensions URL is not properly set. Please go to the <a href="%1">URL setting page</a> and correct it.',
552 [1 => CRM_Utils_System::url('civicrm/admin/setting/url', 'reset=1')]),
553 ts('Extensions url missing'),
554 \Psr\Log\LogLevel::ERROR,
555 'fa-plug'
556 );
557 }
558
559 if (!$extensionSystem->getBrowser()->isEnabled()) {
560 $messages[] = new CRM_Utils_Check_Message(
561 __FUNCTION__,
562 ts('Not checking remote URL for extensions since ext_repo_url is set to false.'),
563 ts('Extensions check disabled'),
564 \Psr\Log\LogLevel::NOTICE,
565 'fa-plug'
566 );
567 return $messages;
568 }
569
570 try {
571 $remotes = $extensionSystem->getBrowser()->getExtensions();
572 }
573 catch (CRM_Extension_Exception $e) {
574 $messages[] = new CRM_Utils_Check_Message(
575 __FUNCTION__,
576 $e->getMessage(),
577 ts('Extension download error'),
578 \Psr\Log\LogLevel::ERROR,
579 'fa-plug'
580 );
581 return $messages;
582 }
583
584 if (!$remotes) {
585 // CRM-13141 There may not be any compatible extensions available for the requested CiviCRM version + CMS. If so, $extdir is empty so just return a notice.
586 $messages[] = new CRM_Utils_Check_Message(
587 __FUNCTION__,
588 ts('There are currently no extensions on the CiviCRM public extension directory which are compatible with version %1. If you want to install an extension which is not marked as compatible, you may be able to <a %2>download and install extensions manually</a> (depending on access to your web server).', [
589 1 => CRM_Utils_System::majorVersion(),
590 2 => 'href="http://wiki.civicrm.org/confluence/display/CRMDOC/Extensions"',
591 ]),
592 ts('No Extensions Available for this Version'),
593 \Psr\Log\LogLevel::NOTICE,
594 'fa-plug'
595 );
596 return $messages;
597 }
598
599 $keys = array_keys($manager->getStatuses());
600 sort($keys);
601 $updates = $errors = $okextensions = [];
602
603 foreach ($keys as $key) {
604 try {
605 $obj = $mapper->keyToInfo($key);
606 }
607 catch (CRM_Extension_Exception $ex) {
608 $errors[] = ts('Failed to read extension (%1). Please refresh the extension list.', [1 => $key]);
609 continue;
610 }
611 $row = CRM_Admin_Page_Extensions::createExtendedInfo($obj);
612 switch ($row['status']) {
613 case CRM_Extension_Manager::STATUS_INSTALLED_MISSING:
614 $errors[] = ts('%1 extension (%2) is installed but missing files.', [1 => CRM_Utils_Array::value('label', $row), 2 => $key]);
615 break;
616
617 case CRM_Extension_Manager::STATUS_INSTALLED:
618 if (!empty($remotes[$key]) && version_compare($row['version'], $remotes[$key]->version, '<')) {
619 $updates[] = ts('%1 (%2) version %3 is installed. <a %4>Upgrade to version %5</a>.', [
620 1 => CRM_Utils_Array::value('label', $row),
621 2 => $key,
622 3 => $row['version'],
623 4 => 'href="' . CRM_Utils_System::url('civicrm/admin/extensions', "action=update&id=$key&key=$key") . '"',
624 5 => $remotes[$key]->version,
625 ]);
626 }
627 else {
628 if (empty($row['label'])) {
629 $okextensions[] = $key;
630 }
631 else {
632 $okextensions[] = ts('%1 (%2) version %3', [
633 1 => $row['label'],
634 2 => $key,
635 3 => $row['version'],
636 ]);
637 }
638 }
639 break;
640 }
641 }
642
643 if (!$okextensions && !$updates && !$errors) {
644 $messages[] = new CRM_Utils_Check_Message(
645 'extensionsOk',
646 ts('No extensions installed. <a %1>Browse available extensions</a>.', [
647 1 => 'href="' . CRM_Utils_System::url('civicrm/admin/extensions', 'reset=1') . '"',
648 ]),
649 ts('Extensions'),
650 \Psr\Log\LogLevel::INFO,
651 'fa-plug'
652 );
653 }
654
655 if ($errors) {
656 $messages[] = new CRM_Utils_Check_Message(
657 __FUNCTION__ . 'Error',
658 '<ul><li>' . implode('</li><li>', $errors) . '</li></ul>',
659 ts('Extension Error'),
660 \Psr\Log\LogLevel::ERROR,
661 'fa-plug'
662 );
663 }
664
665 if ($updates) {
666 $messages[] = new CRM_Utils_Check_Message(
667 'extensionUpdates',
668 '<ul><li>' . implode('</li><li>', $updates) . '</li></ul>',
669 ts('Extension Update Available', ['plural' => '%count Extension Updates Available', 'count' => count($updates)]),
670 \Psr\Log\LogLevel::WARNING,
671 'fa-plug'
672 );
673 }
674
675 if ($okextensions) {
676 if ($updates || $errors) {
677 $message = ts('1 extension is up-to-date:', ['plural' => '%count extensions are up-to-date:', 'count' => count($okextensions)]);
678 }
679 else {
680 $message = ts('All extensions are up-to-date:');
681 }
682 $messages[] = new CRM_Utils_Check_Message(
683 'extensionsOk',
684 $message . '<ul><li>' . implode('</li><li>', $okextensions) . '</li></ul>',
685 ts('Extensions'),
686 \Psr\Log\LogLevel::INFO,
687 'fa-plug'
688 );
689 }
690
691 return $messages;
692 }
693
694 /**
695 * Checks if there are pending extension upgrades.
696 *
697 * @return array
698 */
699 public function checkExtensionUpgrades() {
700 if (CRM_Extension_Upgrades::hasPending()) {
701 $message = new CRM_Utils_Check_Message(
702 __FUNCTION__,
703 ts('Extension upgrades should be run as soon as possible.'),
704 ts('Extension Upgrades Pending'),
705 \Psr\Log\LogLevel::ERROR,
706 'fa-plug'
707 );
708 $message->addAction(
709 ts('Run Upgrades'),
710 ts('Run extension upgrades now?'),
711 'href',
712 ['path' => 'civicrm/admin/extensions/upgrade', 'query' => ['reset' => 1, 'destination' => CRM_Utils_System::url('civicrm/a/#/status')]]
713 );
714 return [$message];
715 }
716 return [];
717 }
718
719 /**
720 * Checks if CiviCRM database version is up-to-date
721 * @return array
722 */
723 public function checkDbVersion() {
724 $messages = [];
725 $dbVersion = CRM_Core_BAO_Domain::version();
726 $upgradeUrl = CRM_Utils_System::url("civicrm/upgrade", "reset=1");
727
728 if (!$dbVersion) {
729 // if db.ver missing
730 $messages[] = new CRM_Utils_Check_Message(
731 __FUNCTION__,
732 ts('Version information found to be missing in database. You will need to determine the correct version corresponding to your current database state.'),
733 ts('Database Version Missing'),
734 \Psr\Log\LogLevel::ERROR,
735 'fa-database'
736 );
737 }
738 elseif (!CRM_Utils_System::isVersionFormatValid($dbVersion)) {
739 $messages[] = new CRM_Utils_Check_Message(
740 __FUNCTION__,
741 ts('Database is marked with invalid version format. You may want to investigate this before you proceed further.'),
742 ts('Database Version Invalid'),
743 \Psr\Log\LogLevel::ERROR,
744 'fa-database'
745 );
746 }
747 elseif (stripos($dbVersion, 'upgrade')) {
748 // if db.ver indicates a partially upgraded db
749 $messages[] = new CRM_Utils_Check_Message(
750 __FUNCTION__,
751 ts('Database check failed - the database looks to have been partially upgraded. You must reload the database with the backup and try the <a href=\'%1\'>upgrade process</a> again.', [1 => $upgradeUrl]),
752 ts('Database Partially Upgraded'),
753 \Psr\Log\LogLevel::ALERT,
754 'fa-database'
755 );
756 }
757 else {
758 $codeVersion = CRM_Utils_System::version();
759
760 // if db.ver < code.ver, time to upgrade
761 if (version_compare($dbVersion, $codeVersion) < 0) {
762 $messages[] = new CRM_Utils_Check_Message(
763 __FUNCTION__,
764 ts('New codebase version detected. You must visit <a href=\'%1\'>upgrade screen</a> to upgrade the database.', [1 => $upgradeUrl]),
765 ts('Database Upgrade Required'),
766 \Psr\Log\LogLevel::ALERT,
767 'fa-database'
768 );
769 }
770
771 // if db.ver > code.ver, sth really wrong
772 if (version_compare($dbVersion, $codeVersion) > 0) {
773 $messages[] = new CRM_Utils_Check_Message(
774 __FUNCTION__,
775 ts('Your database is marked with an unexpected version number: %1. The v%2 codebase may not be compatible with your database state.
776 You will need to determine the correct version corresponding to your current database state. You may want to revert to the codebase
777 you were using until you resolve this problem.<br/>OR if this is a manual install from git, you might want to fix civicrm-version.php file.',
778 [1 => $dbVersion, 2 => $codeVersion]
779 ),
780 ts('Database In Unexpected Version'),
781 \Psr\Log\LogLevel::ERROR,
782 'fa-database'
783 );
784 }
785 }
786
787 return $messages;
788 }
789
790 /**
791 * ensure that all CiviCRM tables are InnoDB
792 * @return array
793 */
794 public function checkDbEngine() {
795 $messages = [];
796
797 if (CRM_Core_DAO::isDBMyISAM(150)) {
798 $messages[] = new CRM_Utils_Check_Message(
799 __FUNCTION__,
800 ts('Your database is configured to use the MyISAM database engine. CiviCRM requires InnoDB. You will need to convert any MyISAM tables in your database to InnoDB. Using MyISAM tables will result in data integrity issues.'),
801 ts('MyISAM Database Engine'),
802 \Psr\Log\LogLevel::ERROR,
803 'fa-database'
804 );
805 }
806 return $messages;
807 }
808
809 /**
810 * ensure reply id is set to any default value
811 * @return array
812 */
813 public function checkReplyIdForMailing() {
814 $messages = [];
815
816 if (!CRM_Mailing_PseudoConstant::defaultComponent('Reply', '')) {
817 $messages[] = new CRM_Utils_Check_Message(
818 __FUNCTION__,
819 ts('Reply Auto Responder is not set to any default value in <a %1>Headers, Footers, and Automated Messages</a>. This will disable the submit operation on any mailing created from CiviMail.', [1 => 'href="' . CRM_Utils_System::url('civicrm/admin/component', 'reset=1') . '"']),
820 ts('No Default value for Auto Responder.'),
821 \Psr\Log\LogLevel::WARNING,
822 'fa-reply'
823 );
824 }
825 return $messages;
826 }
827
828 /**
829 * Check for required mbstring extension
830 * @return array
831 */
832 public function checkMbstring() {
833 $messages = [];
834
835 if (!function_exists('mb_substr')) {
836 $messages[] = new CRM_Utils_Check_Message(
837 __FUNCTION__,
838 ts('The PHP Multibyte String extension is needed for CiviCRM to correctly handle user input among other functionality. Ask your system administrator to install it.'),
839 ts('Missing mbstring Extension'),
840 \Psr\Log\LogLevel::WARNING,
841 'fa-server'
842 );
843 }
844 return $messages;
845 }
846
847 /**
848 * Check if environment is Production.
849 * @return array
850 */
851 public function checkEnvironment() {
852 $messages = [];
853
854 $environment = CRM_Core_Config::environment();
855 if ($environment != 'Production') {
856 $messages[] = new CRM_Utils_Check_Message(
857 __FUNCTION__,
858 ts('The environment of this CiviCRM instance is set to \'%1\'. Certain functionality like scheduled jobs has been disabled.', [1 => $environment]),
859 ts('Non-Production Environment'),
860 \Psr\Log\LogLevel::ALERT,
861 'fa-bug'
862 );
863 }
864 return $messages;
865 }
866
867 /**
868 * Check that the resource URL points to the correct location.
869 * @return array
870 */
871 public function checkResourceUrl() {
872 $messages = [];
873 // Skip when run during unit tests, you can't check without a CMS.
874 if (CRM_Core_Config::singleton()->userFramework == 'UnitTests') {
875 return $messages;
876 }
877 // CRM-21629 Set User Agent to avoid being blocked by filters
878 stream_context_set_default([
879 'http' => ['user_agent' => 'CiviCRM'],
880 ]);
881
882 // Does arrow.png exist where we expect it?
883 $arrowUrl = CRM_Core_Config::singleton()->userFrameworkResourceURL . 'packages/jquery/css/images/arrow.png';
884 if ($this->fileExists($arrowUrl) === FALSE) {
885 $messages[] = new CRM_Utils_Check_Message(
886 __FUNCTION__,
887 ts('The Resource URL is not set correctly. Please set the <a href="%1">CiviCRM Resource URL</a>.',
888 [1 => CRM_Utils_System::url('civicrm/admin/setting/url', 'reset=1')]),
889 ts('Incorrect Resource URL'),
890 \Psr\Log\LogLevel::ERROR,
891 'fa-server'
892 );
893 }
894 return $messages;
895 }
896
897 /**
898 * Check for utf8mb4 support by MySQL.
899 *
900 * @return array<CRM_Utils_Check_Message> an empty array, or a list of warnings
901 */
902 public function checkMysqlUtf8mb4() {
903 $messages = [];
904
905 if (CRM_Core_DAO::getConnection()->phptype != 'mysqli') {
906 return $messages;
907 }
908
909 // Use mysqli_query() to avoid logging an error message.
910 if (mysqli_query(CRM_Core_DAO::getConnection()->connection, 'CREATE TEMPORARY TABLE civicrm_utf8mb4_test (id VARCHAR(255), PRIMARY KEY(id(255))) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC ENGINE=INNODB')) {
911 CRM_Core_DAO::executeQuery('DROP TEMPORARY TABLE civicrm_utf8mb4_test');
912 }
913 else {
914 $messages[] = new CRM_Utils_Check_Message(
915 __FUNCTION__,
916 ts('Future versions of CiviCRM may require MySQL utf8mb4 support. 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'),
917 ts('MySQL utf8mb4 Support'),
918 \Psr\Log\LogLevel::WARNING,
919 'fa-database'
920 );
921 }
922 // Ensure that the MySQL driver supports utf8mb4 encoding.
923 $version = mysqli_get_client_info();
924 if (strpos($version, 'mysqlnd') !== FALSE) {
925 // The mysqlnd driver supports utf8mb4 starting at version 5.0.9.
926 $version = preg_replace('/^\D+([\d.]+).*/', '$1', $version);
927 if (version_compare($version, '5.0.9', '<')) {
928 $messages[] = new CRM_Utils_Check_Message(
929 __FUNCTION__ . 'mysqlnd',
930 ts('It is recommended, though not yet required, to upgrade your PHP MySQL driver (mysqlnd) to >= 5.0.9 for utf8mb4 support.'),
931 ts('PHP MySQL Driver (mysqlnd)'),
932 \Psr\Log\LogLevel::WARNING,
933 'fa-server'
934 );
935 }
936 }
937 else {
938 // The libmysqlclient driver supports utf8mb4 starting at version 5.5.3.
939 if (version_compare($version, '5.5.3', '<')) {
940 $messages[] = new CRM_Utils_Check_Message(
941 __FUNCTION__ . 'libmysqlclient',
942 ts('It is recommended, though not yet required, to upgrade your PHP MySQL driver (libmysqlclient) to >= 5.5.3 for utf8mb4 support.'),
943 ts('PHP MySQL Driver (libmysqlclient)'),
944 \Psr\Log\LogLevel::WARNING,
945 'fa-server'
946 );
947 }
948 }
949
950 return $messages;
951 }
952
953 }