Skip to content
Snippets Groups Projects
automatic_updates.module 11.3 KiB
Newer Older
 * Contains hook implementations for Automatic Updates.
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\automatic_updates\Validation\AdminReadinessMessages;
use Drupal\Core\Form\FormStateInterface;
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>';
      $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>';
Ted Bowman's avatar
Ted Bowman committed
      $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>';
      $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;
  }
}

/**
 * Implements hook_mail().
 */
function automatic_updates_mail(string $key, array &$message, array $params): void {
  // Explicitly pass the language code to all translated strings.
  $options = [
    'langcode' => $message['langcode'],
  ];

    $message['subject'] = t("Drupal core was successfully updated", [], $options);
    $message['body'][] = t('Congratulations!', [], $options);
    $message['body'][] = t('Drupal core was automatically updated from @previous_version to @updated_version.', [
      '@previous_version' => $params['previous_version'],
      '@updated_version' => $params['updated_version'],
    ], $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);
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();
 * 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');
 * 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');
 * Implements hook_form_FORM_ID_alter() for 'update_manager_update_form'.
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'])) {
    $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;
    }

/**
 * 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;
    }
  }
}

/**
 * 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');
  /** @var \Drupal\automatic_updates\ReleaseChooser $recommender */
  $recommender = \Drupal::service('automatic_updates.release_chooser');
  try {
    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'];
  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'];
    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
}