3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
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 +--------------------------------------------------------------------+
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
17 class CRM_Utils_Check_Component_Env
extends CRM_Utils_Check_Component
{
20 * @return CRM_Utils_Check_Message[]
22 public function checkPhpVersion() {
24 $phpVersion = phpversion();
26 if (version_compare($phpVersion, CRM_Upgrade_Incremental_General
::RECOMMENDED_PHP_VER
) >= 0) {
27 $messages[] = new CRM_Utils_Check_Message(
29 ts('This system uses PHP version %1 which meets or exceeds the recommendation of %2.',
32 2 => preg_replace(';^(\d+\.\d+(?:\.[1-9]\d*)?).*$;', '\1', CRM_Upgrade_Incremental_General
::RECOMMENDED_PHP_VER
),
35 \Psr\Log\LogLevel
::INFO
,
39 elseif (version_compare($phpVersion, CRM_Upgrade_Incremental_General
::MIN_RECOMMENDED_PHP_VER
) >= 0) {
40 $messages[] = new CRM_Utils_Check_Message(
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.',
45 2 => CRM_Upgrade_Incremental_General
::RECOMMENDED_PHP_VER
,
47 ts('PHP Out-of-Date'),
48 \Psr\Log\LogLevel
::NOTICE
,
52 elseif (version_compare($phpVersion, CRM_Upgrade_Incremental_General
::MIN_INSTALL_PHP_VER
) >= 0) {
53 $messages[] = new CRM_Utils_Check_Message(
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.',
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
),
61 ts('PHP Out-of-Date'),
62 \Psr\Log\LogLevel
::WARNING
,
67 $messages[] = new CRM_Utils_Check_Message(
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.',
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
),
75 ts('PHP Out-of-Date'),
76 \Psr\Log\LogLevel
::ERROR
,
85 * @return CRM_Utils_Check_Message[]
87 public function checkPhpMysqli() {
90 if (!extension_loaded('mysqli')) {
91 $messages[] = new CRM_Utils_Check_Message(
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>.',
95 1 => 'https://civicrm.org/blog/totten/psa-please-verify-php-extension-mysqli',
98 ts('Forward Compatibility: Enable "mysqli"'),
99 \Psr\Log\LogLevel
::WARNING
,
108 * Check that the MySQL time settings match the PHP time settings.
110 * @return CRM_Utils_Check_Message[]
112 public function checkMysqlTime() {
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(
120 ts('Timestamps reported by MySQL (eg "%1") and PHP (eg "%2" ) are mismatched.', [
123 ]) . '<br />' . CRM_Utils_System
::docURL2('sysadmin/requirements/#mysql-time'),
124 ts('Timestamp Mismatch'),
125 \Psr\Log\LogLevel
::ERROR
,
134 * @return CRM_Utils_Check_Message[]
136 public function checkDebug() {
137 $config = CRM_Core_Config
::singleton();
138 if ($config->debug
) {
139 $message = new CRM_Utils_Check_Message(
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
,
148 ts('Disable Debug Mode'),
149 ts('Disable debug mode now?'),
151 ['Setting', 'create', ['debug_enabled' => 0]]
161 * @return CRM_Utils_Check_Message[]
163 public function checkOutboundMail($force = FALSE) {
166 // CiviMail doesn't work in non-production environments; skip.
167 if (!$force && CRM_Core_Config
::environment() != 'Production') {
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
)
177 $messages[] = new CRM_Utils_Check_Message(
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
,
191 * Check that domain email and org name are set
193 * @return CRM_Utils_Check_Message[]
195 public function checkDomainNameEmail($force = FALSE) {
198 // CiviMail doesn't work in non-production environments; skip.
199 if (!$force && CRM_Core_Config
::environment() != 'Production') {
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");
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).",
219 $msg = ts('Please enter a <a href="%1">default FROM Email Address</a> (for system-generated emails).',
220 [1 => $fixEmailUrl]);
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]);
229 $messages[] = new CRM_Utils_Check_Message(
232 ts('Organization Setup'),
233 \Psr\Log\LogLevel
::WARNING
,
242 * Checks if a default bounce handling mailbox is set up
244 * @return CRM_Utils_Check_Message[]
246 public function checkDefaultMailbox($force = FALSE) {
249 // CiviMail doesn't work in non-production environments; skip.
250 if (!$force && CRM_Core_Config
::environment() != 'Production') {
254 $config = CRM_Core_Config
::singleton();
256 if (in_array('CiviMail', $config->enableComponents
) &&
257 CRM_Core_BAO_MailSettings
::defaultDomain() == "EXAMPLE.ORG"
259 $message = new CRM_Utils_Check_Message(
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
,
268 ts('A default mailbox must be configured for email bounce processing.') . '<br />' .
269 CRM_Utils_System
::docURL2('user/advanced-configuration/email-system-configuration/')
271 $messages[] = $message;
278 * Checks if cron has run in the past hour (3600 seconds)
280 * @return CRM_Utils_Check_Message[]
281 * @throws CRM_Core_Exception
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()) {
291 // Cron doesn't work in non-production environments; skip.
292 if (!$force && CRM_Core_Config
::environment() != 'Production') {
296 $statusPreference = new CRM_Core_DAO_StatusPreference();
297 $statusPreference->domain_id
= CRM_Core_Config
::domainID();
298 $statusPreference->name
= __FUNCTION__
;
300 $level = \Psr\Log\LogLevel
::INFO
;
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
))]);
307 // If cron record doesn't exist, this is a new install. Make a placeholder record (prefs='new').
309 $statusPreference = CRM_Core_BAO_StatusPreference
::create([
310 'name' => __FUNCTION__
,
311 'check_info' => $now,
315 $lastCron = $statusPreference->check_info
;
317 if ($statusPreference->prefs
!== 'new' && $lastCron > $now - 3600) {
318 $title = ts('Cron Running OK');
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
;
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
;
333 $msg .= '<p>' . ts('A cron job is required to execute scheduled jobs automatically.') .
334 '<br />' . CRM_Utils_System
::docURL2('sysadmin/setup/jobs/') . '</p>';
337 $messages[] = new CRM_Utils_Check_Message(
348 * Recommend that sites use path-variables for their directories and URLs.
349 * @return CRM_Utils_Check_Message[]
351 public function checkUrlVariables() {
353 $hasOldStyle = FALSE;
355 'userFrameworkResourceURL',
361 foreach ($settingNames as $settingName) {
362 $settingValue = Civi
::settings()->get($settingName);
363 if (!empty($settingValue) && $settingValue[0] != '[') {
370 $message = new CRM_Utils_Check_Message(
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
,
378 $messages[] = $message;
385 * Recommend that sites use path-variables for their directories and URLs.
386 * @return CRM_Utils_Check_Message[]
388 public function checkDirVariables() {
390 $hasOldStyle = FALSE;
394 'customFileUploadDir',
400 foreach ($settingNames as $settingName) {
401 $settingValue = Civi
::settings()->get($settingName);
402 if (!empty($settingValue) && $settingValue[0] != '[') {
409 $message = new CRM_Utils_Check_Message(
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
,
417 $messages[] = $message;
424 * Check that important directories are writable.
426 * @return CRM_Utils_Check_Message[]
428 public function checkDirsWritable() {
431 $config = CRM_Core_Config
::singleton();
433 'uploadDir' => ts('Temporary Files Directory'),
434 'imageUploadDir' => ts('Images Directory'),
435 'customFileUploadDir' => ts('Custom Files Directory'),
438 foreach ($directories as $directory => $label) {
439 $file = CRM_Utils_File
::createFakeFile($config->$directory);
441 if ($file === FALSE) {
442 $notWritable[] = "$label ({$config->$directory})";
445 $dirWithSlash = CRM_Utils_File
::addTrailingSlash($config->$directory);
446 unlink($dirWithSlash . $file);
452 if (!empty($notWritable)) {
453 $messages[] = new CRM_Utils_Check_Message(
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.',
460 ts('Directory not writable', [
461 'count' => count($notWritable),
462 'plural' => 'Directories not writable',
464 \Psr\Log\LogLevel
::ERROR
,
473 * Checks if new versions are available
475 * @return CRM_Utils_Check_Message[]
476 * @throws CRM_Core_Exception
478 public function checkVersion($force = FALSE) {
481 $vc = new CRM_Utils_VersionCheck();
482 $vc->initialize($force);
484 catch (Exception
$e) {
485 $messages[] = new CRM_Utils_Check_Message(
487 ts('Directory %1 is not writable. Please change your file permissions.',
488 [1 => dirname($vc->cacheFile
)]),
489 ts('Directory not writable'),
490 \Psr\Log\LogLevel
::ERROR
,
496 // Show a notice if the version_check job is disabled
497 if (!$force && empty($vc->cronJob
['is_active'])) {
498 $args = empty($vc->cronJob
['id']) ?
['reset' => 1] : ['reset' => 1, 'action' => 'update', 'id' => $vc->cronJob
['id']];
499 $messages[] = new CRM_Utils_Check_Message(
500 'checkVersionDisabled',
501 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) . '"']),
502 ts('Update Check Disabled'),
503 \Psr\Log\LogLevel
::NOTICE
,
508 if ($vc->isInfoAvailable
) {
509 foreach ($vc->getVersionMessages() ??
[] as $msg) {
510 $messages[] = new CRM_Utils_Check_Message(__FUNCTION__
. '_' . $msg['name'],
511 $msg['message'], $msg['title'], $msg['severity'], 'fa-cloud-upload');
519 * Checks if extensions are set up properly
520 * @return CRM_Utils_Check_Message[]
522 public function checkExtensions() {
524 $extensionSystem = CRM_Extension_System
::singleton();
525 $mapper = $extensionSystem->getMapper();
526 $manager = $extensionSystem->getManager();
528 if ($extensionSystem->getDefaultContainer()) {
529 $basedir = $extensionSystem->getDefaultContainer()->baseDir
;
532 if (empty($basedir)) {
533 // no extension directory
534 $messages[] = new CRM_Utils_Check_Message(
536 ts('Your extensions directory is not set. Click <a href="%1">here</a> to set the extensions directory.',
537 [1 => CRM_Utils_System
::url('civicrm/admin/setting/path', 'reset=1')]),
538 ts('Directory not writable'),
539 \Psr\Log\LogLevel
::NOTICE
,
545 if (!is_dir($basedir)) {
546 $messages[] = new CRM_Utils_Check_Message(
548 ts('Your extensions directory path points to %1, which is not a directory. Please check your file system.',
550 ts('Extensions directory incorrect'),
551 \Psr\Log\LogLevel
::ERROR
,
556 elseif (!is_writable($basedir)) {
557 $messages[] = new CRM_Utils_Check_Message(
558 __FUNCTION__
. 'Writable',
559 ts('Your extensions directory (%1) is read-only. If you would like to perform downloads or upgrades, then change the file permissions.',
561 ts('Read-Only Extensions'),
562 \Psr\Log\LogLevel
::NOTICE
,
567 if (empty($extensionSystem->getDefaultContainer()->baseUrl
)) {
568 $messages[] = new CRM_Utils_Check_Message(
569 __FUNCTION__
. 'URL',
570 ts('The extensions URL is not properly set. Please go to the <a href="%1">URL setting page</a> and correct it.',
571 [1 => CRM_Utils_System
::url('civicrm/admin/setting/url', 'reset=1')]),
572 ts('Extensions url missing'),
573 \Psr\Log\LogLevel
::ERROR
,
578 if (!$extensionSystem->getBrowser()->isEnabled()) {
579 $messages[] = new CRM_Utils_Check_Message(
581 ts('Not checking remote URL for extensions since ext_repo_url is set to false.'),
582 ts('Extensions check disabled'),
583 \Psr\Log\LogLevel
::NOTICE
,
590 $remotes = $extensionSystem->getBrowser()->getExtensions();
592 catch (CRM_Extension_Exception
$e) {
593 $messages[] = new CRM_Utils_Check_Message(
596 ts('Extension download error'),
597 \Psr\Log\LogLevel
::ERROR
,
603 $stauses = $manager->getStatuses();
604 $keys = array_keys($stauses);
605 $enabled = array_keys(array_filter($stauses, function($status) {
606 return $status === CRM_Extension_Manager
::STATUS_INSTALLED
;
609 $updates = $errors = $okextensions = [];
611 foreach ($keys as $key) {
613 $obj = $mapper->keyToInfo($key);
615 catch (CRM_Extension_Exception
$ex) {
616 $errors[] = ts('Failed to read extension (%1). Please refresh the extension list.', [1 => $key]);
619 $row = CRM_Admin_Page_Extensions
::createExtendedInfo($obj);
620 switch ($row['status']) {
621 case CRM_Extension_Manager
::STATUS_INSTALLED_MISSING
:
622 $errors[] = ts('%1 extension (%2) is installed but missing files.', [1 => $row['label'] ??
NULL, 2 => $key]);
625 case CRM_Extension_Manager
::STATUS_INSTALLED
:
626 if (!empty($row['requires']) && array_diff($row['requires'], $enabled)) {
627 $errors[] = ts('%1 extension depends on %2, which is not enabled.', [1 => $row['label'] ??
$key, 2 => implode(', ', array_diff($row['requires'], $enabled))]);
629 elseif (!empty($remotes[$key]) && version_compare($row['version'], $remotes[$key]->version
, '<')) {
630 $updates[] = $row['label'] . ': ' . $mapper->getUpgradeLink($remotes[$key], $row);
633 if (empty($row['label'])) {
634 $okextensions[] = $key;
637 $okextensions[] = ts('%1: Version %2', [
639 2 => $row['version'],
647 if (!$okextensions && !$updates && !$errors) {
648 $messages[] = new CRM_Utils_Check_Message(
650 ts('No extensions installed. <a %1>Browse available extensions</a>.', [
651 1 => 'href="' . CRM_Utils_System
::url('civicrm/admin/extensions', 'reset=1') . '"',
654 \Psr\Log\LogLevel
::INFO
,
660 $messages[] = new CRM_Utils_Check_Message(
661 __FUNCTION__
. 'Error',
662 '<ul><li>' . implode('</li><li>', $errors) . '</li></ul>',
663 ts('Extension Error'),
664 \Psr\Log\LogLevel
::ERROR
,
670 $messages[] = new CRM_Utils_Check_Message(
671 __FUNCTION__
. 'Updates',
672 '<ul><li>' . implode('</li><li>', $updates) . '</li></ul>',
673 ts('Extension Update Available', ['plural' => '%count Extension Updates Available', 'count' => count($updates)]),
674 \Psr\Log\LogLevel
::WARNING
,
680 if ($updates ||
$errors) {
681 $message = ts('1 extension is up-to-date:', ['plural' => '%count extensions are up-to-date:', 'count' => count($okextensions)]);
684 $message = ts('All extensions are up-to-date:');
686 natcasesort($okextensions);
687 $messages[] = new CRM_Utils_Check_Message(
689 $message . '<ul><li>' . implode('</li><li>', $okextensions) . '</li></ul>',
691 \Psr\Log\LogLevel
::INFO
,
700 * @return CRM_Utils_Check_Message[]
702 public function checkScheduledJobLogErrors() {
703 $jobs = civicrm_api3('Job', 'get', [
705 'return' => ["id", "name", "last_run"],
707 'options' => ['limit' => 0],
710 foreach ($jobs['values'] as $job) {
711 $lastExecutionMessage = civicrm_api3('JobLog', 'get', [
713 'return' => ["description"],
714 'job_id' => $job['id'],
715 'options' => ['sort' => "id desc", 'limit' => 1],
716 ])['values'][0]['description'] ??
NULL;
717 if (!empty($lastExecutionMessage) && strpos($lastExecutionMessage, 'Failure') !== FALSE) {
718 $viewLogURL = CRM_Utils_System
::url('civicrm/admin/joblog', "jid={$job['id']}&reset=1");
720 <td>' . $job['name'] . ' </td>
721 <td>' . $lastExecutionMessage . '</td>
722 <td>' . $job['last_run'] . '</td>
723 <td><a href="' . $viewLogURL . '">' . ts('View Job Log') . '</a></td>
731 $message = '<p>' . ts('The following scheduled jobs failed on the last run:') . '</p>
732 <p><table><thead><tr><th>' . ts('Job') . '</th><th>' . ts('Message') . '</th><th>' . ts('Last Run') . '</th><th></th>
733 </tr></thead><tbody>' . $html . '
734 </tbody></table></p>';
736 $msg = new CRM_Utils_Check_Message(
739 ts('Scheduled Job Failures'),
740 \Psr\Log\LogLevel
::WARNING
,
748 * Checks if there are pending extension upgrades.
750 * @return CRM_Utils_Check_Message[]
752 public function checkExtensionUpgrades() {
753 if (CRM_Extension_Upgrades
::hasPending()) {
754 $message = new CRM_Utils_Check_Message(
756 ts('Extension upgrades should be run as soon as possible.'),
757 ts('Extension Upgrades Pending'),
758 \Psr\Log\LogLevel
::ERROR
,
763 ts('Run extension upgrades now?'),
765 ['path' => 'civicrm/admin/extensions/upgrade', 'query' => ['reset' => 1, 'destination' => CRM_Utils_System
::url('civicrm/a/#/status')]]
773 * Checks if CiviCRM database version is up-to-date
774 * @return CRM_Utils_Check_Message[]
776 public function checkDbVersion() {
778 $dbVersion = CRM_Core_BAO_Domain
::version();
779 $upgradeUrl = CRM_Utils_System
::url("civicrm/upgrade", "reset=1");
783 $messages[] = new CRM_Utils_Check_Message(
785 ts('Version information found to be missing in database. You will need to determine the correct version corresponding to your current database state.'),
786 ts('Database Version Missing'),
787 \Psr\Log\LogLevel
::ERROR
,
791 elseif (!CRM_Utils_System
::isVersionFormatValid($dbVersion)) {
792 $messages[] = new CRM_Utils_Check_Message(
794 ts('Database is marked with invalid version format. You may want to investigate this before you proceed further.'),
795 ts('Database Version Invalid'),
796 \Psr\Log\LogLevel
::ERROR
,
800 elseif (stripos($dbVersion, 'upgrade')) {
801 // if db.ver indicates a partially upgraded db
802 $messages[] = new CRM_Utils_Check_Message(
804 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]),
805 ts('Database Partially Upgraded'),
806 \Psr\Log\LogLevel
::ALERT
,
811 // if db.ver < code.ver, time to upgrade
812 if (CRM_Core_BAO_Domain
::isDBUpdateRequired()) {
813 $messages[] = new CRM_Utils_Check_Message(
815 ts('New codebase version detected. You must visit <a href=\'%1\'>upgrade screen</a> to upgrade the database.', [1 => $upgradeUrl]),
816 ts('Database Upgrade Required'),
817 \Psr\Log\LogLevel
::ALERT
,
822 // if db.ver > code.ver, sth really wrong
823 $codeVersion = CRM_Utils_System
::version();
824 if (version_compare($dbVersion, $codeVersion) > 0) {
825 $messages[] = new CRM_Utils_Check_Message(
827 ts('Your database is marked with an unexpected version number: %1. The v%2 codebase may not be compatible with your database state.
828 You will need to determine the correct version corresponding to your current database state. You may want to revert to the codebase
829 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.',
830 [1 => $dbVersion, 2 => $codeVersion]
832 ts('Database In Unexpected Version'),
833 \Psr\Log\LogLevel
::ERROR
,
843 * Ensure that all CiviCRM tables are InnoDB
844 * @return CRM_Utils_Check_Message[]
846 public function checkDbEngine() {
849 if (CRM_Core_DAO
::isDBMyISAM(150)) {
850 $messages[] = new CRM_Utils_Check_Message(
852 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.'),
853 ts('MyISAM Database Engine'),
854 \Psr\Log\LogLevel
::ERROR
,
862 * Ensure reply id is set to any default value
864 * @return CRM_Utils_Check_Message[]
866 public function checkReplyIdForMailing($force = FALSE) {
869 // CiviMail doesn't work in non-production environments; skip.
870 if (!$force && CRM_Core_Config
::environment() != 'Production') {
874 if (!CRM_Mailing_PseudoConstant
::defaultComponent('Reply', '')) {
875 $messages[] = new CRM_Utils_Check_Message(
877 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') . '"']),
878 ts('No Default value for Auto Responder.'),
879 \Psr\Log\LogLevel
::WARNING
,
887 * Check for required mbstring extension
888 * @return CRM_Utils_Check_Message[]
890 public function checkMbstring() {
893 if (!function_exists('mb_substr')) {
894 $messages[] = new CRM_Utils_Check_Message(
896 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.'),
897 ts('Missing mbstring Extension'),
898 \Psr\Log\LogLevel
::WARNING
,
906 * Check if environment is Production.
907 * @return CRM_Utils_Check_Message[]
909 public function checkEnvironment() {
912 $environment = CRM_Core_Config
::environment();
913 if ($environment != 'Production') {
914 $messages[] = new CRM_Utils_Check_Message(
916 ts('The environment of this CiviCRM instance is set to \'%1\'. Certain functionality like scheduled jobs has been disabled.', [1 => $environment]),
917 ts('Non-Production Environment'),
918 \Psr\Log\LogLevel
::NOTICE
,
926 * Check for utf8mb4 support by MySQL.
928 * @return CRM_Utils_Check_Message[]
930 public function checkMysqlUtf8mb4() {
933 if (CRM_Core_DAO
::getConnection()->phptype
!= 'mysqli') {
937 // Use mysqli_query() to avoid logging an error message.
938 $mb4testTableName = CRM_Utils_SQL_TempTable
::build()->setCategory('utf8mb4test')->getName();
939 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')) {
940 CRM_Core_DAO
::executeQuery('DROP TEMPORARY TABLE ' . $mb4testTableName);
943 $messages[] = new CRM_Utils_Check_Message(
945 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'),
946 ts('MySQL Emoji Support (utf8mb4)'),
947 \Psr\Log\LogLevel
::WARNING
,
951 // Ensure that the MySQL driver supports utf8mb4 encoding.
952 $version = mysqli_get_client_info();
953 if (strpos($version, 'mysqlnd') !== FALSE) {
954 // The mysqlnd driver supports utf8mb4 starting at version 5.0.9.
955 $version = preg_replace('/^\D+([\d.]+).*/', '$1', $version);
956 if (version_compare($version, '5.0.9', '<')) {
957 $messages[] = new CRM_Utils_Check_Message(
958 __FUNCTION__
. 'mysqlnd',
959 ts('It is recommended, though not yet required, to upgrade your PHP MySQL driver (mysqlnd) to >= 5.0.9 for utf8mb4 support.'),
960 ts('PHP MySQL Driver (mysqlnd)'),
961 \Psr\Log\LogLevel
::WARNING
,
967 // The libmysqlclient driver supports utf8mb4 starting at version 5.5.3.
968 if (version_compare($version, '5.5.3', '<')) {
969 $messages[] = new CRM_Utils_Check_Message(
970 __FUNCTION__
. 'libmysqlclient',
971 ts('It is recommended, though not yet required, to upgrade your PHP MySQL driver (libmysqlclient) to >= 5.5.3 for utf8mb4 support.'),
972 ts('PHP MySQL Driver (libmysqlclient)'),
973 \Psr\Log\LogLevel
::WARNING
,
982 public function checkMysqlVersion() {
984 $version = CRM_Utils_SQL
::getDatabaseVersion();
985 $minRecommendedVersion = CRM_Upgrade_Incremental_General
::MIN_RECOMMENDED_MYSQL_VER
;
986 $mariaDbRecommendedVersion = '10.1';
987 $upcomingCiviChangeVersion = '5.34';
988 if (version_compare(CRM_Utils_SQL
::getDatabaseVersion(), $minRecommendedVersion, '<')) {
989 $messages[] = new CRM_Utils_Check_Message(
991 ts('To prepare for CiviCRM v%4, please upgrade MySQL. The recommended version will be MySQL v%2 or MariaDB v%3.', [
993 2 => $minRecommendedVersion . '+',
994 3 => $mariaDbRecommendedVersion . '+',
995 4 => $upcomingCiviChangeVersion . '+',
997 ts('MySQL Out-of-Date'),
998 \Psr\Log\LogLevel
::NOTICE
,
1005 public function checkPHPIntlExists() {
1007 if (!extension_loaded('intl')) {
1008 $messages[] = new CRM_Utils_Check_Message(
1010 ts('This system currently does not have the PHP-Intl extension enabled. Please contact your system administrator about getting the extension enabled.'),
1011 ts('Missing PHP Extension: INTL'),
1012 \Psr\Log\LogLevel
::WARNING
,