Skip to content
Snippets Groups Projects
Forked from project / automatic_updates
735 commits behind the upstream repository.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
automatic_updates.module 7.98 KiB
<?php

/**
 * @file
 * Contains hook implementations for Automatic Updates.
 */

use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\automatic_updates\CronUpdater;
use Drupal\automatic_updates\UpdateRecommender;
use Drupal\automatic_updates\Validation\AdminReadinessMessages;
use Drupal\Core\Extension\ExtensionVersion;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\update\ProjectSecurityData;

/**
 * Implements hook_help().
 */
function automatic_updates_help($route_name, RouteMatchInterface $route_match) {
  // @todo Fully document all the modules features in
  //   https://www.drupal.org/i/3253591.
  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>' . 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_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']);
  }
}

/**
 * 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');
  $checker_manager->runIfNoStoredResults();
}

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

/**
 * 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_form_FORM_ID_alter() for 'update_settings' form.
 */
function automatic_updates_form_update_settings_alter(array &$form, FormStateInterface $form_state, string $form_id) {
  $recommender = new UpdateRecommender();
  $drupal_project = $recommender->getProjectInfo();
  $version = ExtensionVersion::createFromVersionString($drupal_project['existing_version']);
  $current_minor = $version->getMajorVersion() . '.' . $version->getMinorVersion();
  // @todo In https://www.drupal.org/node/2998285 use the update XML to
  //   determine when the installed of core will become unsupported.
  $supported_until_version = $version->getMajorVersion() . '.'
      . ((int) $version->getMinorVersion() + ProjectSecurityData::CORE_MINORS_WITH_SECURITY_COVERAGE)
      . '.0';

  $form['automatic_updates_cron'] = [
    '#type' => 'radios',
    '#title' => t('Automatically update Drupal core'),
    '#options' => [
      CronUpdater::DISABLED => t('Disabled'),
      CronUpdater::ALL => t('All supported updates'),
      CronUpdater::SECURITY => t('Security updates only'),
    ],
    '#default_value' => \Drupal::config('automatic_updates.settings')->get('cron'),
    '#description' => t(
      'If enabled, Drupal core will be automatically updated when an update is available. Automatic updates are only supported for @current_minor.x versions of Drupal core. Drupal @current_minor will receive security updates until @supported_until_version is released.',
      [
        '@current_minor' => $current_minor,
        '@supported_until_version' => $supported_until_version,
      ]
    ),
  ];
  $form += [
    '#submit' => ['::submitForm'],
  ];
  $form['#submit'][] = '_automatic_updates_update_settings_form_submit';
}

/**
 * Submit function for the 'update_settings' form.
 */
function _automatic_updates_update_settings_form_submit(array &$form, FormStateInterface $form_state) {
  \Drupal::configFactory()
    ->getEditable('automatic_updates.settings')
    ->set('cron', $form_state->getValue('automatic_updates_cron'))
    ->save();
}

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