automatic_updates-8.x-2.x-dev/src/Form/UpdaterForm.php

src/Form/UpdaterForm.php
<?php

declare(strict_types=1);

namespace Drupal\automatic_updates\Form;

use Drupal\automatic_updates\BatchProcessor;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\package_manager\Exception\FailureMarkerExistsException;
use Drupal\package_manager\Exception\SandboxException;
use Drupal\package_manager\Exception\SandboxOwnershipException;
use Drupal\package_manager\FailureMarker;
use Drupal\package_manager\ProjectInfo;
use Drupal\automatic_updates\ReleaseChooser;
use Drupal\automatic_updates\UpdateSandboxManager;
use Drupal\update\ProjectRelease;
use Drupal\Core\Batch\BatchBuilder;
use Drupal\Core\Extension\ExtensionVersion;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\State\StateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Drupal\package_manager\ValidationResult;
use Drupal\system\SystemManager;
use Drupal\update\UpdateManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Defines a form to update Drupal core.
 *
 * @internal
 *   Form classes are internal and the form structure may change at any time.
 */
final class UpdaterForm extends UpdateFormBase {

  public function __construct(
    private readonly StateInterface $state,
    private readonly UpdateSandboxManager $sandboxManager,
    private readonly EventDispatcherInterface $eventDispatcher,
    private readonly ReleaseChooser $releaseChooser,
    private readonly RendererInterface $renderer,
    private readonly FailureMarker $failureMarker,
    private readonly ModuleHandlerInterface $moduleHandler,
  ) {}

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'automatic_updates_updater_form';
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('state'),
      $container->get(UpdateSandboxManager::class),
      $container->get('event_dispatcher'),
      $container->get(ReleaseChooser::class),
      $container->get('renderer'),
      $container->get(FailureMarker::class),
      $container->get('module_handler')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    try {
      $this->failureMarker->assertNotExists();
    }
    catch (FailureMarkerExistsException $e) {
      $this->messenger()->addError($e->getMessage());
      return $form;
    }
    if ($this->sandboxManager->isAvailable()) {
      $sandbox_exists = FALSE;
    }
    else {
      $sandbox_exists = TRUE;

      // If there's a stage ID stored in the session, try to claim the stage
      // with it. If we succeed, then an update is already in progress, and the
      // current session started it, so redirect them to the confirmation form.
      $stage_id = $this->getRequest()->getSession()->get(BatchProcessor::STAGE_ID_SESSION_KEY);
      if ($stage_id) {
        try {
          $this->sandboxManager->claim($stage_id);
          return $this->redirect('automatic_updates.confirmation_page', [
            'stage_id' => $stage_id,
          ]);
        }
        catch (SandboxOwnershipException) {
          // We already know a stage exists, even if it's not ours, so we don't
          // have to do anything else here.
        }
      }
    }

    $form['last_check'] = [
      '#theme' => 'update_last_check',
      '#last' => $this->state->get('update.last_check', 0),
    ];
    $project_info = new ProjectInfo('drupal');

    $installed_version = ExtensionVersion::createFromVersionString($project_info->getInstalledVersion());
    try {
      $support_branches = $project_info->getSupportedBranches();
      $releases = [];
      foreach ($support_branches as $support_branch) {
        $support_branch_extension_version = ExtensionVersion::createFromSupportBranch($support_branch);
        if ($support_branch_extension_version->getMajorVersion() === $installed_version->getMajorVersion() && $support_branch_extension_version->getMinorVersion() >= $installed_version->getMinorVersion()) {
          $recent_release_in_minor = $this->releaseChooser->getMostRecentReleaseInMinor($this->sandboxManager, $support_branch . '0');
          if ($recent_release_in_minor) {
            $releases[$support_branch] = $recent_release_in_minor;
          }
        }
      }
    }
    catch (\RuntimeException $e) {
      $form['message'] = [
        '#markup' => $e->getMessage(),
      ];
      return $form;
    }

    if ($form_state->getUserInput() || $sandbox_exists) {
      $results = [];
    }
    else {
      try {
        $results = $this->runStatusCheck($this->sandboxManager, $this->eventDispatcher);
      }
      catch (\Throwable $e) {
        $this->messenger()->addError($e->getMessage());
        return $form;
      }
    }
    $this->displayResults($results, $this->renderer);
    $project = $project_info->getProjectInfo();
    if (empty($releases)) {
      if ($project['status'] === UpdateManagerInterface::CURRENT) {
        $this->messenger()->addMessage($this->t('No update available'));
      }
      else {
        $message = $this->t('Updates were found, but they must be performed manually. See <a href=":url">the list of available updates</a> for more information.', [
          ':url' => Url::fromRoute('update.status')->toString(),
        ]);
        // If the current release is old, but otherwise secure and supported,
        // this should be a regular status message. In any other case, urgent
        // action is needed so flag it as an error.
        $this->messenger()->addMessage($message, $project['status'] === UpdateManagerInterface::NOT_CURRENT ? MessengerInterface::TYPE_STATUS : MessengerInterface::TYPE_ERROR);
      }
      return $form;
    }

    if (empty($project['title']) || empty($project['link'])) {
      throw new \UnexpectedValueException('Expected project data to have a title and link.');
    }

    $form['title'] = [
      '#type' => 'html_tag',
      '#tag' => 'h2',
      '#value' => $this->t(
        'Update <a href=":url">Drupal core</a>',
        [':url' => $project['link']],
      ),
    ];
    $form['current'] = [
      '#type' => 'html_tag',
      '#tag' => 'p',
      '#value' => $this->t(
        'Currently installed: @version (@status)',
        [
          '@version' => $project_info->getInstalledVersion(),
          '@status' => $this->getUpdateStatus($project['status']),
        ]
      ),
    ];

    switch ($project['status']) {
      case UpdateManagerInterface::NOT_SECURE:
      case UpdateManagerInterface::REVOKED:
        $release_status = $this->t('Security update');
        $type = 'update-security';
        break;

      default:
        $release_status = $this->t('Available update');
        $type = 'update-recommended';
    }
    $create_update_buttons = !$sandbox_exists && ValidationResult::getOverallSeverity($results) !== SystemManager::REQUIREMENT_ERROR;

    $installed_minor_release = FALSE;
    $next_minor_release_count = 0;
    foreach ($releases as $release) {
      $release_version = ExtensionVersion::createFromVersionString($release->getVersion());
      if ($release_version->getMinorVersion() === $installed_version->getMinorVersion()) {
        $installed_minor_release = TRUE;
        $installed_version = ExtensionVersion::createFromVersionString($project_info->getInstalledVersion());
        $form['installed_minor'] = $this->createReleaseTable(
          $release,
          $release_status,
          $this->t('Latest version of Drupal @major.@minor (currently installed):', [
            '@major' => $installed_version->getMajorVersion(),
            '@minor' => $installed_version->getMinorVersion(),
          ]),
          $type,
          $create_update_buttons,
          // Any update in the current minor should be the primary update.
          TRUE,
        );
      }
      else {
        $next_minor_release_count++;
        if ($next_minor_release_count === 1) {
          if ($this->moduleHandler->moduleExists('help')) {
            $url = Url::fromRoute('help.page')
              ->setRouteParameter('name', 'automatic_updates')
              ->setOption('fragment', 'minor-update');

            $form['minor_update_help'] = [
              '#markup' => $this->t('The following updates are in newer minor version of Drupal. <a href=":url">Learn more about updating to another minor version.</a>', [
                ':url' => $url->toString(),
              ]),
              '#prefix' => '<p>',
              '#suffix' => '</p>',
            ];
          }
        }
        // If there is no update in the current minor make the button for the
        // next minor primary unless the project status is 'CURRENT' or
        // 'NOT_CURRENT'. 'NOT_CURRENT' does not denote that installed version
        // is not a valid only that there is newer version available.
        if (!isset($is_primary)) {
          $is_primary = !$installed_minor_release && !($project['status'] === UpdateManagerInterface::CURRENT || $project['status'] === UpdateManagerInterface::NOT_CURRENT);
        }
        else {
          $is_primary = FALSE;
        }

        // Since updating to another minor version of Drupal is more
        // disruptive than updating within the currently installed minor
        // version, ensure we display a link to the release notes for the
        // first (x.y.0) release of the next minor version, which will inform
        // site owners of any potential pitfalls or major changes. We should
        // always be able to get release info for it; if we can't, that's an
        // error condition.
        $first_release_version = $release_version->getMajorVersion() . '.' . $release_version->getMinorVersion() . '.0';
        $available_updates = update_get_available(TRUE);

        // If the `.0` patch release of this minor is available link to its
        // release notes because this will document the most important changes
        // in this minor.
        if (isset($available_updates['drupal']['releases'][$first_release_version])) {
          $next_minor_first_release = ProjectRelease::createFromArray($available_updates['drupal']['releases'][$first_release_version]);
          $caption = $this->t('Latest version of Drupal @major.@minor (next minor) (<a href=":url">Release notes</a>):', [
            '@major' => $release_version->getMajorVersion(),
            '@minor' => $release_version->getMinorVersion(),
            ':url' => $next_minor_first_release->getReleaseUrl(),
          ]);
        }
        else {
          $caption = $this->t('Latest version of Drupal @major.@minor (next minor):', [
            '@major' => $release_version->getMajorVersion(),
            '@minor' => $release_version->getMinorVersion(),
          ]);
        }

        $form["next_minor_$next_minor_release_count"] = $this->createReleaseTable(
          $release,
          $installed_minor_release ? $this->t('Minor update') : $release_status,
          $caption,
          $installed_minor_release ? 'update-optional' : $type,
          $create_update_buttons,
          $is_primary
        );
      }
    }

    $form['backup'] = [
      '#markup' => $this->t('It\'s a good idea to <a href=":url">back up your database and site code</a> before you begin.', [':url' => 'https://www.drupal.org/node/22281']),
    ];

    if ($sandbox_exists) {
      // If the form has been submitted, do not display this error message
      // because ::deleteExistingUpdate() may run on submit. The message will
      // still be displayed on form build if needed.
      if (!$form_state->getUserInput()) {
        $this->messenger()->addError($this->t('Cannot begin an update because another Composer operation is currently in progress.'));
      }
      $form['actions']['delete'] = [
        '#type' => 'submit',
        '#value' => $this->t('Delete existing update'),
        '#submit' => ['::deleteExistingUpdate'],
      ];
    }
    $form['actions']['#type'] = 'actions';

    return $form;
  }

  /**
   * Submit function to delete an existing in-progress update.
   */
  public function deleteExistingUpdate(): void {
    try {
      $this->sandboxManager->destroy(TRUE);
      $this->messenger()->addMessage($this->t("Staged update deleted"));
    }
    catch (SandboxException $e) {
      $this->messenger()->addError($e->getMessage());
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $button = $form_state->getTriggeringElement();
    $batch = (new BatchBuilder())
      ->setTitle($this->t('Downloading updates'))
      ->setInitMessage($this->t('Preparing to download updates'))
      ->addOperation(
        [BatchProcessor::class, 'begin'],
        [['drupal' => $button['#target_version']]]
      )
      ->addOperation([BatchProcessor::class, 'stage'])
      ->setFinishCallback([BatchProcessor::class, 'finishStage'])
      ->toArray();

    batch_set($batch);
  }

  /**
   * Gets the update table for a specific release.
   *
   * @param \Drupal\update\ProjectRelease $release
   *   The project release.
   * @param \Drupal\Core\StringTranslation\TranslatableMarkup $release_description
   *   The release description.
   * @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $caption
   *   The table caption, if any.
   * @param string $update_type
   *   The update type.
   * @param bool $create_update_button
   *   Whether the update button should be created.
   * @param bool $is_primary
   *   Whether update button should be a primary button.
   *
   * @return string[][]
   *   The table render array.
   */
  private function createReleaseTable(ProjectRelease $release, TranslatableMarkup $release_description, ?TranslatableMarkup $caption, string $update_type, bool $create_update_button, bool $is_primary): array {
    $release_section = ['#type' => 'container'];
    $release_section['table'] = [
      '#type' => 'table',
      '#description' => $this->t('more'),
      '#header' => [
        'title' => [
          'data' => $this->t('Update type'),
          'class' => ['update-project-name'],
        ],
        'target_version' => [
          'data' => $this->t('Version'),
        ],
      ],
    ];
    if ($caption) {
      $release_section['table']['#caption'] = $caption;
    }
    $release_section['table'][$release->getVersion()] = [
      'title' => [
        '#type' => 'html_tag',
        '#tag' => 'p',
        '#value' => $release_description,
      ],
      'target_version' => [
        'data' => [
          '#type' => 'inline_template',
          '#template' => '{{ release_version }} (<a href="{{ release_link }}" title="{{ project_title }}">{{ release_notes }}</a>)',
          '#context' => [
            'release_version' => $release->getVersion(),
            'release_link' => $release->getReleaseUrl(),
            'project_title' => $this->t(
              'Release notes for @project_title @version',
              [
                '@project_title' => 'Drupal core',
                '@version' => $release->getVersion(),
              ]
            ),
            'release_notes' => $this->t('Release notes'),
          ],
        ],
      ],
      '#attributes' => ['class' => ['update-' . $update_type]],
    ];
    if ($create_update_button) {
      $release_section['submit'] = [
        '#type' => 'submit',
        '#value' => $this->t('Update to @version', ['@version' => $release->getVersion()]),
        '#target_version' => $release->getVersion(),
      ];
      if ($is_primary) {
        $release_section['submit']['#button_type'] = 'primary';
      }
    }
    $release_section['#suffix'] = '<br />';
    return $release_section;

  }

  /**
   * Gets the human-readable project status.
   *
   * @param int $status
   *   The project status, one of \Drupal\update\UpdateManagerInterface
   *   constants.
   *
   * @return \Drupal\Core\StringTranslation\TranslatableMarkup
   *   The human-readable status.
   */
  private function getUpdateStatus(int $status): TranslatableMarkup {
    return match ($status) {
      UpdateManagerInterface::NOT_SECURE => $this->t('Security update required!'),
      UpdateManagerInterface::REVOKED => $this->t('Revoked!'),
      UpdateManagerInterface::NOT_SUPPORTED => $this->t('Not supported!'),
      UpdateManagerInterface::NOT_CURRENT => $this->t('Update available'),
      UpdateManagerInterface::CURRENT => $this->t('Up to date'),
      default => $this->t('Unknown status'),
    };
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc