Loading core/modules/update/src/Hook/UpdateCronHooks.php 0 → 100644 +116 −0 Changes for core/modules/update/src/Hook/UpdateCronHooks.php: 116 added lines, 0 removed lines. Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\update\Hook; use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Extension\Requirement\RequirementSeverity; use Drupal\Core\Hook\Attribute\Hook; use Drupal\Core\State\StateInterface; use Drupal\update\MailHandler; use Drupal\update\UpdateManagerInterface; use Drupal\update\UpdateProcessorInterface; use Symfony\Component\DependencyInjection\Attribute\AutowireServiceClosure; /** * Hook implementations for update. */ class UpdateCronHooks { public function __construct( protected readonly ConfigFactoryInterface $configFactory, protected readonly ModuleHandlerInterface $moduleHandler, protected readonly StateInterface $state, protected readonly TimeInterface $time, /** * @var \Closure(): \Drupal\update\MailHandler */ #[AutowireServiceClosure(MailHandler::class)] protected readonly \Closure $mailHandler, /** * @var \Closure(): \Drupal\update\UpdateManagerInterface */ #[AutowireServiceClosure(UpdateManagerInterface::class)] protected readonly \Closure $updateManager, /** * @var \Closure(): \Drupal\update\UpdateProcessorInterface */ #[AutowireServiceClosure(UpdateProcessorInterface::class)] protected readonly \Closure $updateProcessor, ) { } /** * Implements hook_cron(). */ #[Hook('cron')] public function cron(): void { $update_config = $this->configFactory->get('update.settings'); $frequency = $update_config->get('check.interval_days'); $interval = 60 * 60 * 24 * $frequency; $last_check = $this->state->get('update.last_check', 0); $request_time = $this->time->getRequestTime(); if ($request_time - $last_check > $interval) { // If the configured update interval has elapsed, we want to invalidate // the data for all projects, attempt to re-fetch, and trigger any // configured notifications about the new status. ($this->updateManager)()->refreshUpdateData(); ($this->updateProcessor)()->fetchData(); } else { // Otherwise, see if any individual projects are now stale or still // missing data, and if so, try to fetch the data. update_get_available(TRUE); } $last_email_notice = $this->state->get('update.last_email_notification', 0); if ($request_time - $last_email_notice > $interval) { // If configured time between notifications elapsed, send email about // updates possibly available. if ($this->notify()) { // Track when the last mail was successfully sent to avoid sending // too many emails. $this->state->set('update.last_email_notification', $request_time); } } } /** * Performs any notifications that should be done once cron fetches new data. * * This method checks the status of the site using the new data and, depending * on the configuration of the site, notifies administrators via email if * there are new releases or missing security updates. * * @return bool * TRUE if any notifications were sent, otherwise FALSE. * * @see Drupal\update\Hook\UpdateRequirements::runtime() */ protected function notify(): bool { $update_config = $this->configFactory->get('update.settings'); $notify_all = ($update_config->get('notification.threshold') == 'all'); $recipients = $update_config->get('notification.emails'); if (empty($recipients)) { return FALSE; } $status = $this->moduleHandler->invoke('update', 'runtime_requirements'); $items = []; foreach (['core', 'contrib'] as $report_type) { $type = 'update_' . $report_type; if (isset($status[$type]['severity']) && ($status[$type]['severity'] == RequirementSeverity::Error || ($notify_all && $status[$type]['reason'] == UpdateManagerInterface::NOT_CURRENT))) { $items[$report_type] = $status[$type]['reason']; } } if (empty($items)) { return FALSE; } return ($this->mailHandler)()->sendUpdateNotifications($recipients, $items); } } core/modules/update/src/Hook/UpdateHooks.php +1 −32 Changes for core/modules/update/src/Hook/UpdateHooks.php: 1 added line, 32 removed lines. Original line number Diff line number Diff line Loading @@ -108,37 +108,6 @@ public function pageTop(): void { } } /** * Implements hook_cron(). */ #[Hook('cron')] public function cron(): void { $update_config = \Drupal::config('update.settings'); $frequency = $update_config->get('check.interval_days'); $interval = 60 * 60 * 24 * $frequency; $last_check = \Drupal::state()->get('update.last_check', 0); $request_time = \Drupal::time()->getRequestTime(); if ($request_time - $last_check > $interval) { // If the configured update interval has elapsed, we want to invalidate // the data for all projects, attempt to re-fetch, and trigger any // configured notifications about the new status. update_refresh(); update_fetch_data(); } else { // Otherwise, see if any individual projects are now stale or still // missing data, and if so, try to fetch the data. update_get_available(TRUE); } $last_email_notice = \Drupal::state()->get('update.last_email_notification', 0); if ($request_time - $last_email_notice > $interval) { // If configured time between notifications elapsed, send email about // updates possibly available. \Drupal::moduleHandler()->loadInclude('update', 'inc', 'update.fetch'); _update_cron_notify(); } } /** * Implements hook_themes_installed(). * Loading Loading @@ -193,7 +162,7 @@ public function modulesUninstalled($modules): void { * Constructs the email notification message when the site is out of date. * * @see \Drupal\Core\Mail\MailManagerInterface::mail() * @see _update_cron_notify() * @see \Drupal\update\Hook\UpdateCronHooks::notify() * @see _update_message_text() * @see \Drupal\update\UpdateManagerInterface */ Loading core/modules/update/src/Hook/UpdateRequirements.php +4 −5 Changes for core/modules/update/src/Hook/UpdateRequirements.php: 4 added lines, 5 removed lines. Original line number Diff line number Diff line Loading @@ -39,12 +39,11 @@ public function __construct( * attribute, which is an integer constant to indicate why the given status * is being returned (UPDATE_NOT_SECURE, UPDATE_NOT_CURRENT, or * UPDATE_UNKNOWN). This is used for generating the appropriate email * notification messages during update_cron(), and might be useful for other * modules that invoke update_runtime_requirements() to find out if the site * is up to date or not. * notification messages during cron runs, and might be useful for other * modules to find out if the site is up to date or not. * * @see _update_message_text() * @see _update_cron_notify() * @see \Drupal\update\Hook\UpdateCronHooks::notify() * @see \Drupal\update\UpdateManagerInterface */ #[Hook('runtime_requirements')] Loading Loading @@ -100,7 +99,7 @@ public function runtime(): array { * An array to be included in the nested $requirements array. * * @see hook_requirements() * @see update_requirements() * @see Drupal\update\Hook\UpdateRequirements::runtime() * @see update_calculate_project_data() */ protected function requirementCheck($project, $type): array { Loading core/modules/update/src/MailHandler.php 0 → 100644 +67 −0 Changes for core/modules/update/src/MailHandler.php: 67 added lines, 0 removed lines. Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\update; use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Session\AccountInterface; use Symfony\Component\DependencyInjection\Attribute\AutowireServiceClosure; /** * A service to handle assembly and dispatch of Update Status mail messages. * * @internal */ class MailHandler { public function __construct( protected readonly EntityTypeManagerInterface $entityTypeManager, protected readonly LanguageManagerInterface $languageManager, protected readonly TimeInterface $time, /** * @var \Closure(): \Drupal\Core\Mail\MailManagerInterface */ #[AutowireServiceClosure('plugin.manager.mail')] protected readonly \Closure $mailManager, ) { } /** * Sends update notification mails to a list of recipients. * * @param string[] $recipients * A list of recipient mail addresses. * @param array{'core'?: int, 'contrib'?: int} $items * The update status entries for core and contrib projects. Allowed values * are defined as constants on UpdateManagerInterface. * * @return bool * TRUE if any email notifications were sent, otherwise FALSE. * * @see \Drupal\update\UpdateManagerInterface */ public function sendUpdateNotifications(array $recipients, array $items): bool { $results = []; $default_langcode = $this->languageManager->getDefaultLanguage()->getId(); $storage = $this->entityTypeManager->getStorage('user'); $accounts_by_email = []; foreach ($storage->loadByProperties(['mail' => $recipients]) as $account) { assert($account instanceof AccountInterface); $accounts_by_email[$account->getEmail()] = $account; } foreach ($recipients as $recipient) { // If the recipient is a registered user with a language preference, use // the recipient's preferred language. Otherwise, use the system default // language. $recipient_account = $accounts_by_email[$recipient] ?? NULL; $langcode = $recipient_account ? $recipient_account->getPreferredLangcode() : $default_langcode; $message = ($this->mailManager)()->mail('update', 'status_notify', $recipient, $langcode, $items); $results[] = $message['result'] ?? NULL; } return count(array_filter($results)) > 0; } } core/modules/update/src/ProjectSecurityRequirement.php +1 −1 Changes for core/modules/update/src/ProjectSecurityRequirement.php: 1 added line, 1 removed line. Original line number Diff line number Diff line Loading @@ -9,7 +9,7 @@ /** * Class for generating a project's security requirement. * * @see update_requirements() * @see Drupal\update\Hook\UpdateRequirements::runtime() * * @internal * This class implements logic to determine security coverage for Drupal core Loading Loading
core/modules/update/src/Hook/UpdateCronHooks.php 0 → 100644 +116 −0 Changes for core/modules/update/src/Hook/UpdateCronHooks.php: 116 added lines, 0 removed lines. Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\update\Hook; use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Extension\Requirement\RequirementSeverity; use Drupal\Core\Hook\Attribute\Hook; use Drupal\Core\State\StateInterface; use Drupal\update\MailHandler; use Drupal\update\UpdateManagerInterface; use Drupal\update\UpdateProcessorInterface; use Symfony\Component\DependencyInjection\Attribute\AutowireServiceClosure; /** * Hook implementations for update. */ class UpdateCronHooks { public function __construct( protected readonly ConfigFactoryInterface $configFactory, protected readonly ModuleHandlerInterface $moduleHandler, protected readonly StateInterface $state, protected readonly TimeInterface $time, /** * @var \Closure(): \Drupal\update\MailHandler */ #[AutowireServiceClosure(MailHandler::class)] protected readonly \Closure $mailHandler, /** * @var \Closure(): \Drupal\update\UpdateManagerInterface */ #[AutowireServiceClosure(UpdateManagerInterface::class)] protected readonly \Closure $updateManager, /** * @var \Closure(): \Drupal\update\UpdateProcessorInterface */ #[AutowireServiceClosure(UpdateProcessorInterface::class)] protected readonly \Closure $updateProcessor, ) { } /** * Implements hook_cron(). */ #[Hook('cron')] public function cron(): void { $update_config = $this->configFactory->get('update.settings'); $frequency = $update_config->get('check.interval_days'); $interval = 60 * 60 * 24 * $frequency; $last_check = $this->state->get('update.last_check', 0); $request_time = $this->time->getRequestTime(); if ($request_time - $last_check > $interval) { // If the configured update interval has elapsed, we want to invalidate // the data for all projects, attempt to re-fetch, and trigger any // configured notifications about the new status. ($this->updateManager)()->refreshUpdateData(); ($this->updateProcessor)()->fetchData(); } else { // Otherwise, see if any individual projects are now stale or still // missing data, and if so, try to fetch the data. update_get_available(TRUE); } $last_email_notice = $this->state->get('update.last_email_notification', 0); if ($request_time - $last_email_notice > $interval) { // If configured time between notifications elapsed, send email about // updates possibly available. if ($this->notify()) { // Track when the last mail was successfully sent to avoid sending // too many emails. $this->state->set('update.last_email_notification', $request_time); } } } /** * Performs any notifications that should be done once cron fetches new data. * * This method checks the status of the site using the new data and, depending * on the configuration of the site, notifies administrators via email if * there are new releases or missing security updates. * * @return bool * TRUE if any notifications were sent, otherwise FALSE. * * @see Drupal\update\Hook\UpdateRequirements::runtime() */ protected function notify(): bool { $update_config = $this->configFactory->get('update.settings'); $notify_all = ($update_config->get('notification.threshold') == 'all'); $recipients = $update_config->get('notification.emails'); if (empty($recipients)) { return FALSE; } $status = $this->moduleHandler->invoke('update', 'runtime_requirements'); $items = []; foreach (['core', 'contrib'] as $report_type) { $type = 'update_' . $report_type; if (isset($status[$type]['severity']) && ($status[$type]['severity'] == RequirementSeverity::Error || ($notify_all && $status[$type]['reason'] == UpdateManagerInterface::NOT_CURRENT))) { $items[$report_type] = $status[$type]['reason']; } } if (empty($items)) { return FALSE; } return ($this->mailHandler)()->sendUpdateNotifications($recipients, $items); } }
core/modules/update/src/Hook/UpdateHooks.php +1 −32 Changes for core/modules/update/src/Hook/UpdateHooks.php: 1 added line, 32 removed lines. Original line number Diff line number Diff line Loading @@ -108,37 +108,6 @@ public function pageTop(): void { } } /** * Implements hook_cron(). */ #[Hook('cron')] public function cron(): void { $update_config = \Drupal::config('update.settings'); $frequency = $update_config->get('check.interval_days'); $interval = 60 * 60 * 24 * $frequency; $last_check = \Drupal::state()->get('update.last_check', 0); $request_time = \Drupal::time()->getRequestTime(); if ($request_time - $last_check > $interval) { // If the configured update interval has elapsed, we want to invalidate // the data for all projects, attempt to re-fetch, and trigger any // configured notifications about the new status. update_refresh(); update_fetch_data(); } else { // Otherwise, see if any individual projects are now stale or still // missing data, and if so, try to fetch the data. update_get_available(TRUE); } $last_email_notice = \Drupal::state()->get('update.last_email_notification', 0); if ($request_time - $last_email_notice > $interval) { // If configured time between notifications elapsed, send email about // updates possibly available. \Drupal::moduleHandler()->loadInclude('update', 'inc', 'update.fetch'); _update_cron_notify(); } } /** * Implements hook_themes_installed(). * Loading Loading @@ -193,7 +162,7 @@ public function modulesUninstalled($modules): void { * Constructs the email notification message when the site is out of date. * * @see \Drupal\Core\Mail\MailManagerInterface::mail() * @see _update_cron_notify() * @see \Drupal\update\Hook\UpdateCronHooks::notify() * @see _update_message_text() * @see \Drupal\update\UpdateManagerInterface */ Loading
core/modules/update/src/Hook/UpdateRequirements.php +4 −5 Changes for core/modules/update/src/Hook/UpdateRequirements.php: 4 added lines, 5 removed lines. Original line number Diff line number Diff line Loading @@ -39,12 +39,11 @@ public function __construct( * attribute, which is an integer constant to indicate why the given status * is being returned (UPDATE_NOT_SECURE, UPDATE_NOT_CURRENT, or * UPDATE_UNKNOWN). This is used for generating the appropriate email * notification messages during update_cron(), and might be useful for other * modules that invoke update_runtime_requirements() to find out if the site * is up to date or not. * notification messages during cron runs, and might be useful for other * modules to find out if the site is up to date or not. * * @see _update_message_text() * @see _update_cron_notify() * @see \Drupal\update\Hook\UpdateCronHooks::notify() * @see \Drupal\update\UpdateManagerInterface */ #[Hook('runtime_requirements')] Loading Loading @@ -100,7 +99,7 @@ public function runtime(): array { * An array to be included in the nested $requirements array. * * @see hook_requirements() * @see update_requirements() * @see Drupal\update\Hook\UpdateRequirements::runtime() * @see update_calculate_project_data() */ protected function requirementCheck($project, $type): array { Loading
core/modules/update/src/MailHandler.php 0 → 100644 +67 −0 Changes for core/modules/update/src/MailHandler.php: 67 added lines, 0 removed lines. Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\update; use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\Session\AccountInterface; use Symfony\Component\DependencyInjection\Attribute\AutowireServiceClosure; /** * A service to handle assembly and dispatch of Update Status mail messages. * * @internal */ class MailHandler { public function __construct( protected readonly EntityTypeManagerInterface $entityTypeManager, protected readonly LanguageManagerInterface $languageManager, protected readonly TimeInterface $time, /** * @var \Closure(): \Drupal\Core\Mail\MailManagerInterface */ #[AutowireServiceClosure('plugin.manager.mail')] protected readonly \Closure $mailManager, ) { } /** * Sends update notification mails to a list of recipients. * * @param string[] $recipients * A list of recipient mail addresses. * @param array{'core'?: int, 'contrib'?: int} $items * The update status entries for core and contrib projects. Allowed values * are defined as constants on UpdateManagerInterface. * * @return bool * TRUE if any email notifications were sent, otherwise FALSE. * * @see \Drupal\update\UpdateManagerInterface */ public function sendUpdateNotifications(array $recipients, array $items): bool { $results = []; $default_langcode = $this->languageManager->getDefaultLanguage()->getId(); $storage = $this->entityTypeManager->getStorage('user'); $accounts_by_email = []; foreach ($storage->loadByProperties(['mail' => $recipients]) as $account) { assert($account instanceof AccountInterface); $accounts_by_email[$account->getEmail()] = $account; } foreach ($recipients as $recipient) { // If the recipient is a registered user with a language preference, use // the recipient's preferred language. Otherwise, use the system default // language. $recipient_account = $accounts_by_email[$recipient] ?? NULL; $langcode = $recipient_account ? $recipient_account->getPreferredLangcode() : $default_langcode; $message = ($this->mailManager)()->mail('update', 'status_notify', $recipient, $langcode, $items); $results[] = $message['result'] ?? NULL; } return count(array_filter($results)) > 0; } }
core/modules/update/src/ProjectSecurityRequirement.php +1 −1 Changes for core/modules/update/src/ProjectSecurityRequirement.php: 1 added line, 1 removed line. Original line number Diff line number Diff line Loading @@ -9,7 +9,7 @@ /** * Class for generating a project's security requirement. * * @see update_requirements() * @see Drupal\update\Hook\UpdateRequirements::runtime() * * @internal * This class implements logic to determine security coverage for Drupal core Loading