Security update to Drupal 8.4.6
[yaffs-website] / web / core / modules / update / update.authorize.inc
1 <?php
2
3 /**
4  * @file
5  * Callbacks and related functions invoked by authorize.php to update projects.
6  *
7  * We use the Batch API to actually update each individual project on the site.
8  * All of the code in this file is run at a low bootstrap level (modules are not
9  * loaded), so these functions cannot assume access to the rest of the code of
10  * the Update Manager module.
11  */
12
13 use Drupal\Core\Updater\UpdaterException;
14 use Drupal\Core\Url;
15
16 /**
17  * Updates existing projects when invoked by authorize.php.
18  *
19  * Callback for system_authorized_init() in
20  * update_manager_update_ready_form_submit().
21  *
22  * @param $filetransfer
23  *   The FileTransfer object created by authorize.php for use during this
24  *   operation.
25  * @param $projects
26  *   A nested array of projects to install into the live webroot, keyed by
27  *   project name. Each subarray contains the following keys:
28  *   - project: The canonical project short name.
29  *   - updater_name: The name of the Drupal\Core\Updater\Updater class to use
30  *     for this project.
31  *   - local_url: The locally installed location of new code to update with.
32  *
33  * @return \Symfony\Component\HttpFoundation\Response|null
34  *   The result of processing the batch that updates the projects. If this is
35  *   an instance of \Symfony\Component\HttpFoundation\Response the calling code
36  *   should use that response for the current page request.
37  */
38 function update_authorize_run_update($filetransfer, $projects) {
39   $operations = [];
40   foreach ($projects as $project_info) {
41     $operations[] = [
42       'update_authorize_batch_copy_project',
43       [
44         $project_info['project'],
45         $project_info['updater_name'],
46         $project_info['local_url'],
47         $filetransfer,
48       ],
49     ];
50   }
51
52   $batch = [
53     'init_message' => t('Preparing to update your site'),
54     'operations' => $operations,
55     'finished' => 'update_authorize_update_batch_finished',
56     'file' => drupal_get_path('module', 'update') . '/update.authorize.inc',
57   ];
58   batch_set($batch);
59
60   // Since authorize.php has its own method for setting the page title, set it
61   // manually here rather than passing it in to batch_set() as would normally
62   // be done.
63   $_SESSION['authorize_page_title'] = t('Installing updates');
64
65   // Invoke the batch via authorize.php.
66   return system_authorized_batch_process();
67 }
68
69 /**
70  * Installs a new project when invoked by authorize.php.
71  *
72  * Callback for system_authorized_init() in
73  * update_manager_install_form_submit().
74  *
75  * @param FileTransfer $filetransfer
76  *   The FileTransfer object created by authorize.php for use during this
77  *   operation.
78  * @param string $project
79  *   The canonical project short name; i.e., the name of the module, theme, or
80  *   profile.
81  * @param string $updater_name
82  *   The name of the Drupal\Core\Updater\Updater class to use for installing
83  *   this project.
84  * @param string $local_url
85  *   The URL to the locally installed temp directory where the project has
86  *   already been downloaded and extracted into.
87  *
88  * @return \Symfony\Component\HttpFoundation\Response|null
89  *   The result of processing the batch that installs the project. If this is
90  *   an instance of \Symfony\Component\HttpFoundation\Response the calling code
91  *   should use that response for the current page request.
92  */
93 function update_authorize_run_install($filetransfer, $project, $updater_name, $local_url) {
94   $operations[] = [
95     'update_authorize_batch_copy_project',
96     [
97       $project,
98       $updater_name,
99       $local_url,
100       $filetransfer,
101     ],
102   ];
103
104   // @todo Instantiate our Updater to set the human-readable title?
105   $batch = [
106     'init_message' => t('Preparing to install'),
107     'operations' => $operations,
108     // @todo Use a different finished callback for different messages?
109     'finished' => 'update_authorize_install_batch_finished',
110     'file' => drupal_get_path('module', 'update') . '/update.authorize.inc',
111   ];
112   batch_set($batch);
113
114   // Since authorize.php has its own method for setting the page title, set it
115   // manually here rather than passing it in to batch_set() as would normally
116   // be done.
117   $_SESSION['authorize_page_title'] = t('Installing %project', ['%project' => $project]);
118
119   // Invoke the batch via authorize.php.
120   return system_authorized_batch_process();
121 }
122
123 /**
124  * Implements callback_batch_operation().
125  *
126  * Copies project to its proper place when authorized to do so.
127  *
128  * @param string $project
129  *   The canonical short name of the project being installed.
130  * @param string $updater_name
131  *   The name of the Drupal\Core\Updater\Updater class to use for installing
132  *   this project.
133  * @param string $local_url
134  *   The URL to the locally installed temp directory where the project has
135  *   already been downloaded and extracted into.
136  * @param FileTransfer $filetransfer
137  *   The FileTransfer object to use for performing this operation.
138  * @param array $context
139  *   Reference to an array used for Batch API storage.
140  */
141 function update_authorize_batch_copy_project($project, $updater_name, $local_url, $filetransfer, &$context) {
142
143   // Initialize some variables in the Batch API $context array.
144   if (!isset($context['results']['log'])) {
145     $context['results']['log'] = [];
146   }
147   if (!isset($context['results']['log'][$project])) {
148     $context['results']['log'][$project] = [];
149   }
150
151   if (!isset($context['results']['tasks'])) {
152     $context['results']['tasks'] = [];
153   }
154
155   // The batch API uses a session, and since all the arguments are serialized
156   // and unserialized between requests, although the FileTransfer object itself
157   // will be reconstructed, the connection pointer itself will be lost. However,
158   // the FileTransfer object will still have the connection variable, even
159   // though the connection itself is now gone. So, although it's ugly, we have
160   // to unset the connection variable at this point so that the FileTransfer
161   // object will re-initiate the actual connection.
162   unset($filetransfer->connection);
163
164   if (!empty($context['results']['log'][$project]['#abort'])) {
165     $context['finished'] = 1;
166     return;
167   }
168
169   $updater = new $updater_name($local_url, \Drupal::getContainer()->get('update.root'));
170
171   try {
172     if ($updater->isInstalled()) {
173       // This is an update.
174       $tasks = $updater->update($filetransfer);
175     }
176     else {
177       $tasks = $updater->install($filetransfer);
178     }
179   }
180   catch (UpdaterException $e) {
181     _update_batch_create_message($context['results']['log'][$project], t('Error installing / updating'), FALSE);
182     _update_batch_create_message($context['results']['log'][$project], $e->getMessage(), FALSE);
183     $context['results']['log'][$project]['#abort'] = TRUE;
184     return;
185   }
186
187   _update_batch_create_message($context['results']['log'][$project], t('Installed %project_name successfully', ['%project_name' => $project]));
188   if (!empty($tasks)) {
189     $context['results']['tasks'] += $tasks;
190   }
191
192   // This particular operation is now complete, even though the batch might
193   // have other operations to perform.
194   $context['finished'] = 1;
195 }
196
197 /**
198  * Batch callback: Performs actions when the authorized update batch is done.
199  *
200  * This processes the results and stashes them into SESSION such that
201  * authorize.php will render a report. Also responsible for putting the site
202  * back online and clearing the update status storage after a successful update.
203  *
204  * @param $success
205  *   TRUE if the batch operation was successful; FALSE if there were errors.
206  * @param $results
207  *   An associative array of results from the batch operation.
208  */
209 function update_authorize_update_batch_finished($success, $results) {
210   foreach ($results['log'] as $messages) {
211     if (!empty($messages['#abort'])) {
212       $success = FALSE;
213     }
214   }
215   $offline = \Drupal::state()->get('system.maintenance_mode');
216   if ($success) {
217     // Now that the update completed, we need to clear the available update data
218     // and recompute our status, so prevent show bogus results.
219     _update_authorize_clear_update_status();
220
221     // Take the site out of maintenance mode if it was previously that way.
222     if ($offline && isset($_SESSION['maintenance_mode']) && $_SESSION['maintenance_mode'] == FALSE) {
223       \Drupal::state()->set('system.maintenance_mode', FALSE);
224       $page_message = [
225         'message' => t('Update was completed successfully. Your site has been taken out of maintenance mode.'),
226         'type' => 'status',
227       ];
228     }
229     else {
230       $page_message = [
231         'message' => t('Update was completed successfully.'),
232         'type' => 'status',
233       ];
234     }
235   }
236   elseif (!$offline) {
237     $page_message = [
238       'message' => t('Update failed! See the log below for more information.'),
239       'type' => 'error',
240     ];
241   }
242   else {
243     $page_message = [
244       'message' => t('Update failed! See the log below for more information. Your site is still in maintenance mode.'),
245       'type' => 'error',
246     ];
247   }
248   // Since we're doing an update of existing code, always add a task for
249   // running update.php.
250   $url = Url::fromRoute('system.db_update');
251   $results['tasks'][] = t('Your modules have been downloaded and updated.');
252   $results['tasks'][] = [
253     '#type' => 'link',
254     '#url' => $url,
255     '#title' => t('Run database updates'),
256     // Since this is being called outsite of the primary front controller,
257     // the base_url needs to be set explicitly to ensure that links are
258     // relative to the site root.
259     // @todo Simplify with https://www.drupal.org/node/2548095
260     '#options' => [
261       'absolute' => TRUE,
262       'base_url' => $GLOBALS['base_url'],
263     ],
264     '#access' => $url->access(\Drupal::currentUser())
265   ];
266
267   // Unset the variable since it is no longer needed.
268   unset($_SESSION['maintenance_mode']);
269
270   // Set all these values into the SESSION so authorize.php can display them.
271   $_SESSION['authorize_results']['success'] = $success;
272   $_SESSION['authorize_results']['page_message'] = $page_message;
273   $_SESSION['authorize_results']['messages'] = $results['log'];
274   $_SESSION['authorize_results']['tasks'] = $results['tasks'];
275   $_SESSION['authorize_page_title'] = t('Update manager');
276 }
277
278 /**
279  * Implements callback_batch_finished().
280  *
281  * Performs actions when the authorized install batch is done.
282  *
283  * This processes the results and stashes them into SESSION such that
284  * authorize.php will render a report. Also responsible for putting the site
285  * back online after a successful install if necessary.
286  *
287  * @param $success
288  *   TRUE if the batch operation was a success; FALSE if there were errors.
289  * @param $results
290  *   An associative array of results from the batch operation.
291  */
292 function update_authorize_install_batch_finished($success, $results) {
293   foreach ($results['log'] as $messages) {
294     if (!empty($messages['#abort'])) {
295       $success = FALSE;
296     }
297   }
298   $offline = \Drupal::state()->get('system.maintenance_mode');
299   if ($success) {
300     // Take the site out of maintenance mode if it was previously that way.
301     if ($offline && isset($_SESSION['maintenance_mode']) && $_SESSION['maintenance_mode'] == FALSE) {
302       \Drupal::state()->set('system.maintenance_mode', FALSE);
303       $page_message = [
304         'message' => t('Installation was completed successfully. Your site has been taken out of maintenance mode.'),
305         'type' => 'status',
306       ];
307     }
308     else {
309       $page_message = [
310         'message' => t('Installation was completed successfully.'),
311         'type' => 'status',
312       ];
313     }
314   }
315   elseif (!$success && !$offline) {
316     $page_message = [
317       'message' => t('Installation failed! See the log below for more information.'),
318       'type' => 'error',
319     ];
320   }
321   else {
322     $page_message = [
323       'message' => t('Installation failed! See the log below for more information. Your site is still in maintenance mode.'),
324       'type' => 'error',
325     ];
326   }
327
328   // Unset the variable since it is no longer needed.
329   unset($_SESSION['maintenance_mode']);
330
331   // Set all these values into the SESSION so authorize.php can display them.
332   $_SESSION['authorize_results']['success'] = $success;
333   $_SESSION['authorize_results']['page_message'] = $page_message;
334   $_SESSION['authorize_results']['messages'] = $results['log'];
335   $_SESSION['authorize_results']['tasks'] = $results['tasks'];
336   $_SESSION['authorize_page_title'] = t('Update manager');
337 }
338
339 /**
340  * Creates a structure of log messages.
341  *
342  * @param array $project_results
343  *   An associative array of results from the batch operation.
344  * @param string $message
345  *   A string containing a log message.
346  * @param bool $success
347  *   (optional) TRUE if the operation the message is about was a success, FALSE
348  *   if there were errors. Defaults to TRUE.
349  */
350 function _update_batch_create_message(&$project_results, $message, $success = TRUE) {
351   $project_results[] = ['message' => $message, 'success' => $success];
352 }
353
354 /**
355  * Clears available update status data.
356  *
357  * Since this function is run at such a low bootstrap level, the Update Manager
358  * module is not loaded. So, we can't just call update_storage_clear(). However,
359  * the key-value backend is available, so we just call that.
360  *
361  * Note that we do not want to delete  items related to currently pending fetch
362  * attempts.
363  *
364  * @see update_authorize_update_batch_finished()
365  * @see update_storage_clear()
366  */
367 function _update_authorize_clear_update_status() {
368   \Drupal::keyValueExpirable('update')->deleteAll();
369   \Drupal::keyValueExpirable('update_available_release')->deleteAll();
370 }