Newer
Older

Lucas Hedding
committed
<?php
/**
* @file
* Contains hook implementations for Automatic Updates.

Lucas Hedding
committed
*/

Ted Bowman
committed
use Drupal\automatic_updates\BatchProcessor;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\automatic_updates\Validation\AdminReadinessMessages;
use Drupal\Core\Form\FormStateInterface;

Lucas Hedding
committed
use Drupal\Core\Url;

Ted Bowman
committed
use Drupal\system\Controller\DbUpdateController;
/**
* Implements hook_help().
*/
function automatic_updates_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.auto_updates':
$output = '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('Automatic Updates lets you update Drupal core.') . '</p>';

Adam G-H
committed
$output .= '<p>';
$output .= t('Automatic Updates will keep Drupal secure and up-to-date by automatically installing new patch-level updates, if available, when cron runs. It also provides a user interface to check if any updates are available and install them. You can <a href=":configure-form">configure Automatic Updates</a> to install all patch-level updates, only security updates, or no updates at all, during cron. By default, only security updates are installed during cron; this requires that you <a href=":update-form">install non-security updates through the user interface</a>.', [
':configure-form' => Url::fromRoute('update.settings')->toString(),
':update-form' => Url::fromRoute('automatic_updates.report_update')->toString(),
]);
$output .= '</p>';
$output .= '<p>' . t('Additionally, Automatic Updates periodically runs checks to ensure that updates can be installed, and will warn site administrators if problems are detected.') . '</p>';

Adam G-H
committed
$output .= '<h3>' . t('Requirements') . '</h3>';
$output .= '<p>' . t('Automatic Updates requires Composer @version or later available as an executable, and PHP must have permission to run it. The path to the executable may be set in the <code>package_manager.settings:executables.composer</code> config setting, or it will be automatically detected.', ['@version' => ComposerExecutableValidator::MINIMUM_COMPOSER_VERSION]) . '</p>';
$output .= '<p>' . t('For more information, see the <a href=":automatic-updates-documentation">online documentation for the Automatic Updates module</a>.', [':automatic-updates-documentation' => 'https://www.drupal.org/docs/8/update/automatic-updates']) . '</p>';
return $output;
}
}

Adam G-H
committed
/**
* Implements hook_mail().
*/
function automatic_updates_mail(string $key, array &$message, array $params): void {

Theresa Grannum
committed
// Explicitly pass the language code to all translated strings.
$options = [
'langcode' => $message['langcode'],
];

Adam G-H
committed
if ($key === 'cron_successful') {

Theresa Grannum
committed
$message['subject'] = t("Drupal core was successfully updated", [], $options);
$message['body'][] = t('Congratulations!', [], $options);

Adam G-H
committed
$message['body'][] = t('Drupal core was automatically updated from @previous_version to @updated_version.', [
'@previous_version' => $params['previous_version'],
'@updated_version' => $params['updated_version'],

Theresa Grannum
committed
], $options);
$message['body'][] = t('This e-mail was sent by the Automatic Updates module. Unattended updates are not yet fully supported.', [], $options);
$message['body'][] = t('If you are using this feature in production, it is strongly recommended for you to visit your site and ensure that everything still looks good.', [], $options);

Adam G-H
committed
}
}

Lucas Hedding
committed
/**
* Implements hook_page_top().
*/
function automatic_updates_page_top() {
/** @var \Drupal\automatic_updates\Validation\AdminReadinessMessages $readiness_messages */
$readiness_messages = \Drupal::classResolver(AdminReadinessMessages::class);
$readiness_messages->displayAdminPageMessages();
// @todo Rely on the route option after https://www.drupal.org/i/3236497 is
// committed.
// @todo Remove 'system.batch_page.html' after
// https://www.drupal.org/i/3238311 is committed.
$skip_routes = [
'system.batch_page.html',
'automatic_updates.confirmation_page',
'automatic_updates.report_update',
'automatic_updates.module_update',
];
// @see auto_updates_module_implements_alter()
$route_name = \Drupal::routeMatch()->getRouteName();
if (!in_array($route_name, $skip_routes, TRUE) && function_exists('update_page_top')) {
update_page_top();
}
}
/**
* Implements hook_module_implements_alter().
*
* @todo Remove after https://www.drupal.org/i/3236497 is committed.
*/
function automatic_updates_module_implements_alter(&$implementations, $hook) {
if ($hook === 'page_top') {
// Remove hook_page_top() implementation from the Update module. This '
// implementation displays error messages about security releases. We call
// this implementation in our own automatic_updates_page_top() except on our
// own routes to avoid these messages while an update is in progress.
unset($implementations['update']);
}
if ($hook === 'cron') {
// Whatever mofo.
$hook = $implementations['automatic_updates'];
unset($implementations['automatic_updates']);
$implementations['automatic_updates'] = $hook;
}
}
/**
* Implements hook_cron().
*/
function automatic_updates_cron() {
\Drupal::service('automatic_updates.cron_updater')->handleCron();
/** @var \Drupal\automatic_updates\Validation\ReadinessValidationManager $checker_manager */
$checker_manager = \Drupal::service('automatic_updates.readiness_validation_manager');
$last_results = $checker_manager->getResults();
$last_run_time = $checker_manager->getLastRunTime();
// Do not run readiness checks more than once an hour unless there are no
// results available.
if ($last_results === NULL || !$last_run_time || \Drupal::time()->getRequestTime() - $last_run_time > 3600) {
$checker_manager->run();

Lucas Hedding
committed
}

Lucas Hedding
committed
}
/**
* Implements hook_modules_installed().
*/
function automatic_updates_modules_installed() {
// Run the readiness checkers if needed when any modules are installed in
// case they provide readiness checker services.
/** @var \Drupal\automatic_updates\Validation\ReadinessValidationManager $checker_manager */
$checker_manager = \Drupal::service('automatic_updates.readiness_validation_manager');
$checker_manager->run();
}
/**
* Implements hook_modules_uninstalled().
*/
function automatic_updates_modules_uninstalled() {
// Run the readiness checkers if needed when any modules are uninstalled in
// case they provided readiness checker services.
/** @var \Drupal\automatic_updates\Validation\ReadinessValidationManager $checker_manager */
$checker_manager = \Drupal::service('automatic_updates.readiness_validation_manager');
$checker_manager->run();
}

Lucas Hedding
committed
/**
* Implements hook_form_FORM_ID_alter() for 'update_manager_update_form'.

Lucas Hedding
committed
*/
function automatic_updates_form_update_manager_update_form_alter(&$form, FormStateInterface $form_state, $form_id) {
// Remove current message that core updates are not supported with a link to
// use this module's form. The local task to 'update_manager_update_form' is
// replaced by our own from but this original form would still accessible via
// by its original URL.
if (isset($form['manual_updates']['#rows']['drupal']['data']['title'])) {

Kunal Sachdev
committed
$current_route = \Drupal::routeMatch()->getRouteName();
if ($current_route === 'update.module_update') {
$redirect_route = 'automatic_updates.module_update';
}
elseif ($current_route === 'update.report_update') {
$redirect_route = 'automatic_updates.report_update';
}
if (!empty($redirect_route)) {
$core_updates_message = t(
'<h2>Core updates required</h2>Drupal core updates are supported by the enabled <a href="@url">Automatic Updates module</a>',
['@url' => Url::fromRoute($redirect_route)->toString()]
);
$form['manual_updates']['#prefix'] = $core_updates_message;
}

Lucas Hedding
committed
}

Ted Bowman
committed
/**
* Implements hook_local_tasks_alter().
*/
function automatic_updates_local_tasks_alter(array &$local_tasks) {
// The Update module's update form only allows updating modules and themes
// via archive files, which could produce unexpected results on a site using
// our Composer-based updater.
$new_routes = [
'update.report_update' => 'automatic_updates.report_update',
'update.module_update' => 'automatic_updates.module_update',
'update.theme_update' => 'automatic_updates.theme_update',
];
foreach ($new_routes as $local_task_id => $new_route) {
if (!empty($local_tasks[$local_task_id])) {
$local_tasks[$local_task_id]['route_name'] = $new_route;
}
}
}

Ted Bowman
committed
/**
* Implements hook_batch_alter().
*
* @todo Remove this in https://www.drupal.org/i/3267817.
*/
function automatic_updates_batch_alter(array &$batch): void {
foreach ($batch['sets'] as &$batch_set) {
if (!empty($batch_set['finished']) && $batch_set['finished'] === [DbUpdateController::class, 'batchFinished']) {
$batch_set['finished'] = [BatchProcessor::class, 'dbUpdateBatchFinished'];
}
}
}
/**
* Implements hook_preprocess_update_project_status().
*/
function automatic_updates_preprocess_update_project_status(array &$variables) {
$project = &$variables['project'];
if ($project['name'] !== 'drupal') {
return;
}
$updater = \Drupal::service('automatic_updates.updater');

Adam G-H
committed
$supported_target_versions = [];
/** @var \Drupal\automatic_updates\ReleaseChooser $recommender */
$recommender = \Drupal::service('automatic_updates.release_chooser');
try {

Adam G-H
committed
if ($installed_minor_release = $recommender->getLatestInInstalledMinor($updater)) {
$supported_target_versions[] = $installed_minor_release->getVersion();
}
if ($next_minor_release = $recommender->getLatestInNextMinor($updater)) {
$supported_target_versions[] = $next_minor_release->getVersion();
}
}
catch (RuntimeException $exception) {
// If for some reason we are not able to get the update recommendations
// do not alter the report.
watchdog_exception('automatic_updates', $exception);
return;
}
$variables['#attached']['library'][] = 'automatic_updates/update_status';
$status = &$variables['status'];

Adam G-H
committed
if ($supported_target_versions && $status['label']) {
$status['label'] = [
'#markup' => t(
'@label <a href=":update-form">Update now</a>', [
'@label' => $status['label'],
':update-form' => Url::fromRoute('automatic_updates.report_update')->toString(),
]),
];
}
// BEGIN: DELETE FROM CORE MERGE REQUEST
if (empty($variables['versions'])) {
return;
}
foreach ($variables['versions'] as &$themed_version) {
$version_info = &$themed_version['#version'];

Adam G-H
committed
if ($supported_target_versions && in_array($version_info['version'], $supported_target_versions, TRUE)) {
$version_info['download_link'] = Url::fromRoute('automatic_updates.report_update')->setAbsolute()->toString();
}
else {
// If this version will not be displayed as an option on this module's
// update form replace the link to download the archive file with the
// release notes link. The release notes page will provide Composer
// instructions. While this isn't a perfect solution the Update module twig
// templates do not check if 'download_link' is set, so we cannot unset it
// here.
$themed_version['#attributes']['class'][] = 'automatic-updates-unsupported-version';
$version_info['download_link'] = $version_info['release_link'];
}
}
// END: DELETE FROM CORE MERGE REQUEST
}