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