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