Skip to content
Snippets Groups Projects
automatic_updates.module 11.6 KiB
Newer Older
  • Learn to ignore specific revisions
  •  * Contains hook implementations for Automatic Updates.
    
    use Drupal\Core\Routing\RouteMatchInterface;
    
    use Drupal\automatic_updates\Validation\AdminReadinessMessages;
    
    use Drupal\system\Controller\DbUpdateController;
    
    use Drupal\package_manager\Validator\ComposerExecutableValidator;
    
    /**
     * Implements hook_help().
     */
    function automatic_updates_help($route_name, RouteMatchInterface $route_match) {
      switch ($route_name) {
    
          $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('update.report_update')->toString(),
    
    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 a Composer executable whose version satisfies <code>@version</code>, 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_CONSTRAINT]) . '</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>';
    
          $output .= '<p id="cron-alternate-port">' . t('If your site is running on the built-in PHP web server, unattended (i.e., cron) updates may not work without one of the following workarounds:') . '</p>';
          $output .= '<ul>';
          $output .= '<li>' . t('Use a multithreaded web server, such as Apache, NGINX, or on Windows, IIS.') . '</li>';
          $output .= '<li>' . t('Run another instance of the built-in PHP web server on a different port and configure automatic updates accordingly: <code>$config["automatic_updates.settings"]["cron_port"] = $alternate_port_number;</code>') . '</li>';
          $output .= '</ul>';
    
    /**
     * 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'],
      ];
    
      if ($key === 'cron_successful') {
        $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);
      }
      elseif (str_starts_with($key, 'cron_failed')) {
        $message['subject'] = t("Drupal core update failed", [], $options);
    
        // If this is considered urgent, prefix the subject line with a call to
        // action.
        if ($params['urgent']) {
          $message['subject'] = t('URGENT: @subject', [
            '@subject' => $message['subject'],
    
        }
    
        $message['body'][] = t('Drupal core failed to update automatically from @previous_version to @target_version. The following error was logged:', [
          '@previous_version' => $params['previous_version'],
          '@target_version' => $params['target_version'],
        ], $options);
        $message['body'][] = $params['error_message'];
    
        // If the problem was not due to a failed apply, provide a link for the site
        // owner to do the update.
        if ($key !== 'cron_failed_apply') {
          $url = Url::fromRoute('update.report_update')
            ->setAbsolute()
            ->toString();
    
          if ($key === 'cron_failed_insecure') {
            $message['body'][] = t('Your site is running an insecure version of Drupal and should be updated as soon as possible. Visit @url to perform the update.', ['@url' => $url], $options);
          }
          else {
            $message['body'][] = t('No immediate action is needed, but it is recommended that you visit @url to perform the update, or at least check that everything still looks good.', ['@url' => $url], $options);
          }
        }
    
      }
    
      // If this email was related to an unattended update, explicitly state that
      // this isn't supported yet.
      if (str_starts_with($key, 'cron_')) {
    
        $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.
    
      if ($hook === 'cron') {
        $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_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('update.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('update.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
    }