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