"Fossies" - the Fresh Open Source Software Archive

Member "drupal-8.9.10/core/modules/update/src/Form/UpdateManagerUpdate.php" (26 Nov 2020, 13703 Bytes) of package /linux/www/drupal-8.9.10.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "UpdateManagerUpdate.php" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 9.0.8_vs_9.1.0-rc1.

    1 <?php
    2 
    3 namespace Drupal\update\Form;
    4 
    5 use Drupal\Core\Extension\ModuleHandlerInterface;
    6 use Drupal\Core\Form\FormBase;
    7 use Drupal\Core\Form\FormStateInterface;
    8 use Drupal\Core\Link;
    9 use Drupal\Core\State\StateInterface;
   10 use Drupal\Core\Url;
   11 use Drupal\update\UpdateFetcherInterface;
   12 use Drupal\update\UpdateManagerInterface;
   13 use Drupal\update\ModuleVersion;
   14 use Symfony\Component\DependencyInjection\ContainerInterface;
   15 
   16 /**
   17  * Configure update settings for this site.
   18  *
   19  * @internal
   20  */
   21 class UpdateManagerUpdate extends FormBase {
   22 
   23   /**
   24    * The module handler.
   25    *
   26    * @var \Drupal\Core\Extension\ModuleHandlerInterface
   27    */
   28   protected $moduleHandler;
   29 
   30   /**
   31    * The Drupal state storage service.
   32    *
   33    * @var \Drupal\Core\State\StateInterface
   34    */
   35   protected $state;
   36 
   37   /**
   38    * Constructs a new UpdateManagerUpdate object.
   39    *
   40    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   41    *   The module handler.
   42    * @param \Drupal\Core\State\StateInterface $state
   43    *   The state service.
   44    */
   45   public function __construct(ModuleHandlerInterface $module_handler, StateInterface $state) {
   46     $this->moduleHandler = $module_handler;
   47     $this->state = $state;
   48   }
   49 
   50   /**
   51    * {@inheritdoc}
   52    */
   53   public function getFormId() {
   54     return 'update_manager_update_form';
   55   }
   56 
   57   /**
   58    * {@inheritdoc}
   59    */
   60   public static function create(ContainerInterface $container) {
   61     return new static(
   62       $container->get('module_handler'),
   63       $container->get('state')
   64     );
   65   }
   66 
   67   /**
   68    * {@inheritdoc}
   69    */
   70   public function buildForm(array $form, FormStateInterface $form_state) {
   71     $this->moduleHandler->loadInclude('update', 'inc', 'update.manager');
   72 
   73     $last_markup = [
   74       '#theme' => 'update_last_check',
   75       '#last' => $this->state->get('update.last_check') ?: 0,
   76     ];
   77     $form['last_check'] = [
   78       '#markup' => \Drupal::service('renderer')->render($last_markup),
   79     ];
   80 
   81     if (!_update_manager_check_backends($form, 'update')) {
   82       return $form;
   83     }
   84 
   85     $available = update_get_available(TRUE);
   86     if (empty($available)) {
   87       $form['message'] = [
   88         '#markup' => $this->t('There was a problem getting update information. Try again later.'),
   89       ];
   90       return $form;
   91     }
   92 
   93     $form['#attached']['library'][] = 'update/drupal.update.admin';
   94 
   95     // This will be a nested array. The first key is the kind of project, which
   96     // can be either 'enabled', 'disabled', 'manual' (projects which require
   97     // manual updates, such as core). Then, each subarray is an array of
   98     // projects of that type, indexed by project short name, and containing an
   99     // array of data for cells in that project's row in the appropriate table.
  100     $projects = [];
  101 
  102     // This stores the actual download link we're going to update from for each
  103     // project in the form, regardless of if it's enabled or disabled.
  104     $form['project_downloads'] = ['#tree' => TRUE];
  105     $this->moduleHandler->loadInclude('update', 'inc', 'update.compare');
  106     $project_data = update_calculate_project_data($available);
  107     foreach ($project_data as $name => $project) {
  108       // Filter out projects which are up to date already.
  109       if ($project['status'] == UpdateManagerInterface::CURRENT) {
  110         continue;
  111       }
  112       // The project name to display can vary based on the info we have.
  113       if (!empty($project['title'])) {
  114         if (!empty($project['link'])) {
  115           $project_name = Link::fromTextAndUrl($project['title'], Url::fromUri($project['link']))->toString();
  116         }
  117         else {
  118           $project_name = $project['title'];
  119         }
  120       }
  121       elseif (!empty($project['info']['name'])) {
  122         $project_name = $project['info']['name'];
  123       }
  124       else {
  125         $project_name = $name;
  126       }
  127       if ($project['project_type'] == 'theme' || $project['project_type'] == 'theme-disabled') {
  128         $project_name .= ' ' . $this->t('(Theme)');
  129       }
  130 
  131       if (empty($project['recommended'])) {
  132         // If we don't know what to recommend they upgrade to, we should skip
  133         // the project entirely.
  134         continue;
  135       }
  136 
  137       $recommended_release = $project['releases'][$project['recommended']];
  138       $recommended_version = '{{ release_version }} (<a href="{{ release_link }}" title="{{ project_title }}">{{ release_notes }}</a>)';
  139       $recommended_version_parser = ModuleVersion::createFromVersionString($recommended_release['version']);
  140       if ($recommended_version_parser->getMajorVersion() != $project['existing_major']) {
  141         $recommended_version .= '<div title="{{ major_update_warning_title }}" class="update-major-version-warning">{{ major_update_warning_text }}</div>';
  142       }
  143 
  144       $recommended_version = [
  145         '#type' => 'inline_template',
  146         '#template' => $recommended_version,
  147         '#context' => [
  148           'release_version' => $recommended_release['version'],
  149           'release_link' => $recommended_release['release_link'],
  150           'project_title' => $this->t('Release notes for @project_title', ['@project_title' => $project['title']]),
  151           'major_update_warning_title' => $this->t('Major upgrade warning'),
  152           'major_update_warning_text' => $this->t('This update is a major version update which means that it may not be backwards compatible with your currently running version. It is recommended that you read the release notes and proceed at your own risk.'),
  153           'release_notes' => $this->t('Release notes'),
  154         ],
  155       ];
  156 
  157       // Create an entry for this project.
  158       $entry = [
  159         'title' => $project_name,
  160         'installed_version' => $project['existing_version'],
  161         'recommended_version' => ['data' => $recommended_version],
  162       ];
  163 
  164       switch ($project['status']) {
  165         case UpdateManagerInterface::NOT_SECURE:
  166         case UpdateManagerInterface::REVOKED:
  167           $entry['title'] .= ' ' . $this->t('(Security update)');
  168           $entry['#weight'] = -2;
  169           $type = 'security';
  170           break;
  171 
  172         case UpdateManagerInterface::NOT_SUPPORTED:
  173           $type = 'unsupported';
  174           $entry['title'] .= ' ' . $this->t('(Unsupported)');
  175           $entry['#weight'] = -1;
  176           break;
  177 
  178         case UpdateFetcherInterface::UNKNOWN:
  179         case UpdateFetcherInterface::NOT_FETCHED:
  180         case UpdateFetcherInterface::NOT_CHECKED:
  181         case UpdateManagerInterface::NOT_CURRENT:
  182           $type = 'recommended';
  183           break;
  184 
  185         default:
  186           // Jump out of the switch and onto the next project in foreach.
  187           continue 2;
  188       }
  189 
  190       // Use the project title for the tableselect checkboxes.
  191       $entry['title'] = [
  192         'data' => [
  193           '#title' => $entry['title'],
  194           '#markup' => $entry['title'],
  195         ],
  196       ];
  197       $entry['#attributes'] = ['class' => ['update-' . $type]];
  198 
  199       // Drupal core needs to be upgraded manually.
  200       $needs_manual = $project['project_type'] == 'core';
  201 
  202       // If the recommended release for a contributed project is not compatible
  203       // with the currently installed version of core, list that project in a
  204       // separate table. To determine if the release is compatible, we inspect
  205       // the 'core_compatible' key from the release info array. If it's not
  206       // defined, it means we can't determine compatibility requirements (or
  207       // we're looking at core), so we assume it is compatible.
  208       $compatible = $recommended_release['core_compatible'] ?? TRUE;
  209 
  210       if ($needs_manual) {
  211         $this->removeCheckboxFromRow($entry);
  212         $projects['manual'][$name] = $entry;
  213       }
  214       elseif (!$compatible) {
  215         $this->removeCheckboxFromRow($entry);
  216         // If the release has a core_compatibility_message, inject it.
  217         if (!empty($recommended_release['core_compatibility_message'])) {
  218           // @todo In https://www.drupal.org/project/drupal/issues/3121769
  219           //   refactor this into something theme-friendly so we don't have a
  220           //   classless <div> here.
  221           $entry['data']['recommended_version']['data']['#template'] .= ' <div>{{ core_compatibility_message }}</div>';
  222           $entry['data']['recommended_version']['data']['#context']['core_compatibility_message'] = $recommended_release['core_compatibility_message'];
  223         }
  224         $projects['not-compatible'][$name] = $entry;
  225       }
  226       else {
  227         $form['project_downloads'][$name] = [
  228           '#type' => 'value',
  229           '#value' => $recommended_release['download_link'],
  230         ];
  231 
  232         // Based on what kind of project this is, save the entry into the
  233         // appropriate subarray.
  234         switch ($project['project_type']) {
  235           case 'module':
  236           case 'theme':
  237             $projects['enabled'][$name] = $entry;
  238             break;
  239 
  240           case 'module-disabled':
  241           case 'theme-disabled':
  242             $projects['disabled'][$name] = $entry;
  243             break;
  244         }
  245       }
  246     }
  247 
  248     if (empty($projects)) {
  249       $form['message'] = [
  250         '#markup' => $this->t('All of your projects are up to date.'),
  251       ];
  252       return $form;
  253     }
  254 
  255     $headers = [
  256       'title' => [
  257         'data' => $this->t('Name'),
  258         'class' => ['update-project-name'],
  259       ],
  260       'installed_version' => $this->t('Installed version'),
  261       'recommended_version' => $this->t('Recommended version'),
  262     ];
  263 
  264     if (!empty($projects['enabled'])) {
  265       $form['projects'] = [
  266         '#type' => 'tableselect',
  267         '#header' => $headers,
  268         '#options' => $projects['enabled'],
  269       ];
  270       if (!empty($projects['disabled'])) {
  271         $form['projects']['#prefix'] = '<h2>' . $this->t('Enabled') . '</h2>';
  272       }
  273     }
  274 
  275     if (!empty($projects['disabled'])) {
  276       $form['disabled_projects'] = [
  277         '#type' => 'tableselect',
  278         '#header' => $headers,
  279         '#options' => $projects['disabled'],
  280         '#weight' => 1,
  281         '#prefix' => '<h2>' . $this->t('Disabled') . '</h2>',
  282       ];
  283     }
  284 
  285     // If either table has been printed yet, we need a submit button and to
  286     // validate the checkboxes.
  287     if (!empty($projects['enabled']) || !empty($projects['disabled'])) {
  288       $form['actions'] = ['#type' => 'actions'];
  289       $form['actions']['submit'] = [
  290         '#type' => 'submit',
  291         '#value' => $this->t('Download these updates'),
  292       ];
  293     }
  294 
  295     if (!empty($projects['manual'])) {
  296       $prefix = '<h2>' . $this->t('Manual updates required') . '</h2>';
  297       $prefix .= '<p>' . $this->t('Automatic updates of Drupal core are not supported at this time.') . '</p>';
  298       $form['manual_updates'] = [
  299         '#type' => 'table',
  300         '#header' => $headers,
  301         '#rows' => $projects['manual'],
  302         '#prefix' => $prefix,
  303         '#weight' => 120,
  304       ];
  305     }
  306 
  307     if (!empty($projects['not-compatible'])) {
  308       $form['not_compatible'] = [
  309         '#type' => 'table',
  310         '#header' => $headers,
  311         '#rows' => $projects['not-compatible'],
  312         '#prefix' => '<h2>' . $this->t('Not compatible') . '</h2>',
  313         '#weight' => 150,
  314       ];
  315     }
  316 
  317     return $form;
  318   }
  319 
  320   /**
  321    * Prepares a row entry for use in a regular table, not a 'tableselect'.
  322    *
  323    * There are no checkboxes in the 'Manual updates' or 'Not compatible' tables,
  324    * so they will be rendered by '#theme' => 'table', not 'tableselect'. Since
  325    * the data formats are incompatible, this method converts to the format
  326    * expected by '#theme' => 'table'. Generally, rows end up in the main tables
  327    * that have a checkbox to allow the site admin to select which missing
  328    * updates to install. This method is only used for the special case tables
  329    * that have no such checkbox.
  330    *
  331    * @todo In https://www.drupal.org/project/drupal/issues/3121775 refactor
  332    *   self::buildForm() so that we don't need this method at all.
  333    *
  334    * @param array[] $row
  335    *   The render array for a table row.
  336    */
  337   protected function removeCheckboxFromRow(array &$row) {
  338     unset($row['#weight']);
  339     $attributes = $row['#attributes'];
  340     unset($row['#attributes']);
  341     $row = [
  342       'data' => $row,
  343     ] + $attributes;
  344   }
  345 
  346   /**
  347    * {@inheritdoc}
  348    */
  349   public function validateForm(array &$form, FormStateInterface $form_state) {
  350     if (!$form_state->isValueEmpty('projects')) {
  351       $enabled = array_filter($form_state->getValue('projects'));
  352     }
  353     if (!$form_state->isValueEmpty('disabled_projects')) {
  354       $disabled = array_filter($form_state->getValue('disabled_projects'));
  355     }
  356     if (empty($enabled) && empty($disabled)) {
  357       $form_state->setErrorByName('projects', $this->t('You must select at least one project to update.'));
  358     }
  359   }
  360 
  361   /**
  362    * {@inheritdoc}
  363    */
  364   public function submitForm(array &$form, FormStateInterface $form_state) {
  365     $this->moduleHandler->loadInclude('update', 'inc', 'update.manager');
  366     $projects = [];
  367     foreach (['projects', 'disabled_projects'] as $type) {
  368       if (!$form_state->isValueEmpty($type)) {
  369         $projects = array_merge($projects, array_keys(array_filter($form_state->getValue($type))));
  370       }
  371     }
  372     $operations = [];
  373     foreach ($projects as $project) {
  374       $operations[] = [
  375         'update_manager_batch_project_get',
  376         [
  377           $project,
  378           $form_state->getValue(['project_downloads', $project]),
  379         ],
  380       ];
  381     }
  382     $batch = [
  383       'title' => $this->t('Downloading updates'),
  384       'init_message' => $this->t('Preparing to download selected updates'),
  385       'operations' => $operations,
  386       'finished' => 'update_manager_download_batch_finished',
  387       'file' => drupal_get_path('module', 'update') . '/update.manager.inc',
  388     ];
  389     batch_set($batch);
  390   }
  391 
  392 }