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