Mercurial > hg > isophonics-drupal-site
comparison core/modules/system/src/Controller/DbUpdateController.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | 129ea1e6d783 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\system\Controller; | |
4 | |
5 use Drupal\Core\Cache\CacheBackendInterface; | |
6 use Drupal\Core\Controller\ControllerBase; | |
7 use Drupal\Core\Extension\ModuleHandlerInterface; | |
8 use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface; | |
9 use Drupal\Core\Render\BareHtmlPageRendererInterface; | |
10 use Drupal\Core\Session\AccountInterface; | |
11 use Drupal\Core\Site\Settings; | |
12 use Drupal\Core\State\StateInterface; | |
13 use Drupal\Core\Update\UpdateRegistry; | |
14 use Drupal\Core\Url; | |
15 use Symfony\Component\DependencyInjection\ContainerInterface; | |
16 use Symfony\Component\HttpFoundation\Response; | |
17 use Symfony\Component\HttpFoundation\Request; | |
18 | |
19 /** | |
20 * Controller routines for database update routes. | |
21 */ | |
22 class DbUpdateController extends ControllerBase { | |
23 | |
24 /** | |
25 * The keyvalue expirable factory. | |
26 * | |
27 * @var \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface | |
28 */ | |
29 protected $keyValueExpirableFactory; | |
30 | |
31 /** | |
32 * A cache backend interface. | |
33 * | |
34 * @var \Drupal\Core\Cache\CacheBackendInterface | |
35 */ | |
36 protected $cache; | |
37 | |
38 /** | |
39 * The state service. | |
40 * | |
41 * @var \Drupal\Core\State\StateInterface | |
42 */ | |
43 protected $state; | |
44 | |
45 /** | |
46 * The module handler. | |
47 * | |
48 * @var \Drupal\Core\Extension\ModuleHandlerInterface | |
49 */ | |
50 protected $moduleHandler; | |
51 | |
52 /** | |
53 * The current user. | |
54 * | |
55 * @var \Drupal\Core\Session\AccountInterface | |
56 */ | |
57 protected $account; | |
58 | |
59 /** | |
60 * The bare HTML page renderer. | |
61 * | |
62 * @var \Drupal\Core\Render\BareHtmlPageRendererInterface | |
63 */ | |
64 protected $bareHtmlPageRenderer; | |
65 | |
66 /** | |
67 * The app root. | |
68 * | |
69 * @var string | |
70 */ | |
71 protected $root; | |
72 | |
73 /** | |
74 * The post update registry. | |
75 * | |
76 * @var \Drupal\Core\Update\UpdateRegistry | |
77 */ | |
78 protected $postUpdateRegistry; | |
79 | |
80 /** | |
81 * Constructs a new UpdateController. | |
82 * | |
83 * @param string $root | |
84 * The app root. | |
85 * @param \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface $key_value_expirable_factory | |
86 * The keyvalue expirable factory. | |
87 * @param \Drupal\Core\Cache\CacheBackendInterface $cache | |
88 * A cache backend interface. | |
89 * @param \Drupal\Core\State\StateInterface $state | |
90 * The state service. | |
91 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler | |
92 * The module handler. | |
93 * @param \Drupal\Core\Session\AccountInterface $account | |
94 * The current user. | |
95 * @param \Drupal\Core\Render\BareHtmlPageRendererInterface $bare_html_page_renderer | |
96 * The bare HTML page renderer. | |
97 * @param \Drupal\Core\Update\UpdateRegistry $post_update_registry | |
98 * The post update registry. | |
99 */ | |
100 public function __construct($root, KeyValueExpirableFactoryInterface $key_value_expirable_factory, CacheBackendInterface $cache, StateInterface $state, ModuleHandlerInterface $module_handler, AccountInterface $account, BareHtmlPageRendererInterface $bare_html_page_renderer, UpdateRegistry $post_update_registry) { | |
101 $this->root = $root; | |
102 $this->keyValueExpirableFactory = $key_value_expirable_factory; | |
103 $this->cache = $cache; | |
104 $this->state = $state; | |
105 $this->moduleHandler = $module_handler; | |
106 $this->account = $account; | |
107 $this->bareHtmlPageRenderer = $bare_html_page_renderer; | |
108 $this->postUpdateRegistry = $post_update_registry; | |
109 } | |
110 | |
111 /** | |
112 * {@inheritdoc} | |
113 */ | |
114 public static function create(ContainerInterface $container) { | |
115 return new static( | |
116 $container->get('app.root'), | |
117 $container->get('keyvalue.expirable'), | |
118 $container->get('cache.default'), | |
119 $container->get('state'), | |
120 $container->get('module_handler'), | |
121 $container->get('current_user'), | |
122 $container->get('bare_html_page_renderer'), | |
123 $container->get('update.post_update_registry') | |
124 ); | |
125 } | |
126 | |
127 /** | |
128 * Returns a database update page. | |
129 * | |
130 * @param string $op | |
131 * The update operation to perform. Can be any of the below: | |
132 * - info | |
133 * - selection | |
134 * - run | |
135 * - results | |
136 * @param \Symfony\Component\HttpFoundation\Request $request | |
137 * The current request object. | |
138 * | |
139 * @return \Symfony\Component\HttpFoundation\Response | |
140 * A response object object. | |
141 */ | |
142 public function handle($op, Request $request) { | |
143 require_once $this->root . '/core/includes/install.inc'; | |
144 require_once $this->root . '/core/includes/update.inc'; | |
145 | |
146 drupal_load_updates(); | |
147 update_fix_compatibility(); | |
148 | |
149 if ($request->query->get('continue')) { | |
150 $_SESSION['update_ignore_warnings'] = TRUE; | |
151 } | |
152 | |
153 $regions = []; | |
154 $requirements = update_check_requirements(); | |
155 $severity = drupal_requirements_severity($requirements); | |
156 if ($severity == REQUIREMENT_ERROR || ($severity == REQUIREMENT_WARNING && empty($_SESSION['update_ignore_warnings']))) { | |
157 $regions['sidebar_first'] = $this->updateTasksList('requirements'); | |
158 $output = $this->requirements($severity, $requirements, $request); | |
159 } | |
160 else { | |
161 switch ($op) { | |
162 case 'selection': | |
163 $regions['sidebar_first'] = $this->updateTasksList('selection'); | |
164 $output = $this->selection($request); | |
165 break; | |
166 | |
167 case 'run': | |
168 $regions['sidebar_first'] = $this->updateTasksList('run'); | |
169 $output = $this->triggerBatch($request); | |
170 break; | |
171 | |
172 case 'info': | |
173 $regions['sidebar_first'] = $this->updateTasksList('info'); | |
174 $output = $this->info($request); | |
175 break; | |
176 | |
177 case 'results': | |
178 $regions['sidebar_first'] = $this->updateTasksList('results'); | |
179 $output = $this->results($request); | |
180 break; | |
181 | |
182 // Regular batch ops : defer to batch processing API. | |
183 default: | |
184 require_once $this->root . '/core/includes/batch.inc'; | |
185 $regions['sidebar_first'] = $this->updateTasksList('run'); | |
186 $output = _batch_page($request); | |
187 break; | |
188 } | |
189 } | |
190 | |
191 if ($output instanceof Response) { | |
192 return $output; | |
193 } | |
194 $title = isset($output['#title']) ? $output['#title'] : $this->t('Drupal database update'); | |
195 | |
196 return $this->bareHtmlPageRenderer->renderBarePage($output, $title, 'maintenance_page', $regions); | |
197 } | |
198 | |
199 /** | |
200 * Returns the info database update page. | |
201 * | |
202 * @param \Symfony\Component\HttpFoundation\Request $request | |
203 * The current request. | |
204 * | |
205 * @return array | |
206 * A render array. | |
207 */ | |
208 protected function info(Request $request) { | |
209 // Change query-strings on css/js files to enforce reload for all users. | |
210 _drupal_flush_css_js(); | |
211 // Flush the cache of all data for the update status module. | |
212 $this->keyValueExpirableFactory->get('update')->deleteAll(); | |
213 $this->keyValueExpirableFactory->get('update_available_release')->deleteAll(); | |
214 | |
215 $build['info_header'] = [ | |
216 '#markup' => '<p>' . $this->t('Use this utility to update your database whenever a new release of Drupal or a module is installed.') . '</p><p>' . $this->t('For more detailed information, see the <a href="https://www.drupal.org/upgrade">upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.') . '</p>', | |
217 ]; | |
218 | |
219 $info[] = $this->t("<strong>Back up your code</strong>. Hint: when backing up module code, do not leave that backup in the 'modules' or 'sites/*/modules' directories as this may confuse Drupal's auto-discovery mechanism."); | |
220 $info[] = $this->t('Put your site into <a href=":url">maintenance mode</a>.', [ | |
221 ':url' => Url::fromRoute('system.site_maintenance_mode')->toString(TRUE)->getGeneratedUrl(), | |
222 ]); | |
223 $info[] = $this->t('<strong>Back up your database</strong>. This process will change your database values and in case of emergency you may need to revert to a backup.'); | |
224 $info[] = $this->t('Install your new files in the appropriate location, as described in the handbook.'); | |
225 $build['info'] = [ | |
226 '#theme' => 'item_list', | |
227 '#list_type' => 'ol', | |
228 '#items' => $info, | |
229 ]; | |
230 $build['info_footer'] = [ | |
231 '#markup' => '<p>' . $this->t('When you have performed the steps above, you may proceed.') . '</p>', | |
232 ]; | |
233 | |
234 $build['link'] = [ | |
235 '#type' => 'link', | |
236 '#title' => $this->t('Continue'), | |
237 '#attributes' => ['class' => ['button', 'button--primary']], | |
238 // @todo Revisit once https://www.drupal.org/node/2548095 is in. | |
239 '#url' => Url::fromUri('base://selection'), | |
240 ]; | |
241 return $build; | |
242 } | |
243 | |
244 /** | |
245 * Renders a list of available database updates. | |
246 * | |
247 * @param \Symfony\Component\HttpFoundation\Request $request | |
248 * The current request. | |
249 * | |
250 * @return array | |
251 * A render array. | |
252 */ | |
253 protected function selection(Request $request) { | |
254 // Make sure there is no stale theme registry. | |
255 $this->cache->deleteAll(); | |
256 | |
257 $count = 0; | |
258 $incompatible_count = 0; | |
259 $build['start'] = [ | |
260 '#tree' => TRUE, | |
261 '#type' => 'details', | |
262 ]; | |
263 | |
264 // Ensure system.module's updates appear first. | |
265 $build['start']['system'] = []; | |
266 | |
267 $starting_updates = []; | |
268 $incompatible_updates_exist = FALSE; | |
269 $updates_per_module = []; | |
270 foreach (['update', 'post_update'] as $update_type) { | |
271 switch ($update_type) { | |
272 case 'update': | |
273 $updates = update_get_update_list(); | |
274 break; | |
275 case 'post_update': | |
276 $updates = $this->postUpdateRegistry->getPendingUpdateInformation(); | |
277 break; | |
278 } | |
279 foreach ($updates as $module => $update) { | |
280 if (!isset($update['start'])) { | |
281 $build['start'][$module] = [ | |
282 '#type' => 'item', | |
283 '#title' => $module . ' module', | |
284 '#markup' => $update['warning'], | |
285 '#prefix' => '<div class="messages messages--warning">', | |
286 '#suffix' => '</div>', | |
287 ]; | |
288 $incompatible_updates_exist = TRUE; | |
289 continue; | |
290 } | |
291 if (!empty($update['pending'])) { | |
292 $updates_per_module += [$module => []]; | |
293 $updates_per_module[$module] = array_merge($updates_per_module[$module], $update['pending']); | |
294 $build['start'][$module] = [ | |
295 '#type' => 'hidden', | |
296 '#value' => $update['start'], | |
297 ]; | |
298 // Store the previous items in order to merge normal updates and | |
299 // post_update functions together. | |
300 $build['start'][$module] = [ | |
301 '#theme' => 'item_list', | |
302 '#items' => $updates_per_module[$module], | |
303 '#title' => $module . ' module', | |
304 ]; | |
305 | |
306 if ($update_type === 'update') { | |
307 $starting_updates[$module] = $update['start']; | |
308 } | |
309 } | |
310 if (isset($update['pending'])) { | |
311 $count = $count + count($update['pending']); | |
312 } | |
313 } | |
314 } | |
315 | |
316 // Find and label any incompatible updates. | |
317 foreach (update_resolve_dependencies($starting_updates) as $data) { | |
318 if (!$data['allowed']) { | |
319 $incompatible_updates_exist = TRUE; | |
320 $incompatible_count++; | |
321 $module_update_key = $data['module'] . '_updates'; | |
322 if (isset($build['start'][$module_update_key]['#items'][$data['number']])) { | |
323 if ($data['missing_dependencies']) { | |
324 $text = $this->t('This update will been skipped due to the following missing dependencies:') . '<em>' . implode(', ', $data['missing_dependencies']) . '</em>'; | |
325 } | |
326 else { | |
327 $text = $this->t("This update will be skipped due to an error in the module's code."); | |
328 } | |
329 $build['start'][$module_update_key]['#items'][$data['number']] .= '<div class="warning">' . $text . '</div>'; | |
330 } | |
331 // Move the module containing this update to the top of the list. | |
332 $build['start'] = [$module_update_key => $build['start'][$module_update_key]] + $build['start']; | |
333 } | |
334 } | |
335 | |
336 // Warn the user if any updates were incompatible. | |
337 if ($incompatible_updates_exist) { | |
338 drupal_set_message($this->t('Some of the pending updates cannot be applied because their dependencies were not met.'), 'warning'); | |
339 } | |
340 | |
341 if (empty($count)) { | |
342 drupal_set_message($this->t('No pending updates.')); | |
343 unset($build); | |
344 $build['links'] = [ | |
345 '#theme' => 'links', | |
346 '#links' => $this->helpfulLinks($request), | |
347 ]; | |
348 | |
349 // No updates to run, so caches won't get flushed later. Clear them now. | |
350 drupal_flush_all_caches(); | |
351 } | |
352 else { | |
353 $build['help'] = [ | |
354 '#markup' => '<p>' . $this->t('The version of Drupal you are updating from has been automatically detected.') . '</p>', | |
355 '#weight' => -5, | |
356 ]; | |
357 if ($incompatible_count) { | |
358 $build['start']['#title'] = $this->formatPlural( | |
359 $count, | |
360 '1 pending update (@number_applied to be applied, @number_incompatible skipped)', | |
361 '@count pending updates (@number_applied to be applied, @number_incompatible skipped)', | |
362 ['@number_applied' => $count - $incompatible_count, '@number_incompatible' => $incompatible_count] | |
363 ); | |
364 } | |
365 else { | |
366 $build['start']['#title'] = $this->formatPlural($count, '1 pending update', '@count pending updates'); | |
367 } | |
368 // @todo Simplify with https://www.drupal.org/node/2548095 | |
369 $base_url = str_replace('/update.php', '', $request->getBaseUrl()); | |
370 $url = (new Url('system.db_update', ['op' => 'run']))->setOption('base_url', $base_url); | |
371 $build['link'] = [ | |
372 '#type' => 'link', | |
373 '#title' => $this->t('Apply pending updates'), | |
374 '#attributes' => ['class' => ['button', 'button--primary']], | |
375 '#weight' => 5, | |
376 '#url' => $url, | |
377 '#access' => $url->access($this->currentUser()), | |
378 ]; | |
379 } | |
380 | |
381 return $build; | |
382 } | |
383 | |
384 /** | |
385 * Displays results of the update script with any accompanying errors. | |
386 * | |
387 * @param \Symfony\Component\HttpFoundation\Request $request | |
388 * The current request. | |
389 * | |
390 * @return array | |
391 * A render array. | |
392 */ | |
393 protected function results(Request $request) { | |
394 // @todo Simplify with https://www.drupal.org/node/2548095 | |
395 $base_url = str_replace('/update.php', '', $request->getBaseUrl()); | |
396 | |
397 // Report end result. | |
398 $dblog_exists = $this->moduleHandler->moduleExists('dblog'); | |
399 if ($dblog_exists && $this->account->hasPermission('access site reports')) { | |
400 $log_message = $this->t('All errors have been <a href=":url">logged</a>.', [ | |
401 ':url' => Url::fromRoute('dblog.overview')->setOption('base_url', $base_url)->toString(TRUE)->getGeneratedUrl(), | |
402 ]); | |
403 } | |
404 else { | |
405 $log_message = $this->t('All errors have been logged.'); | |
406 } | |
407 | |
408 if (!empty($_SESSION['update_success'])) { | |
409 $message = '<p>' . $this->t('Updates were attempted. If you see no failures below, you may proceed happily back to your <a href=":url">site</a>. Otherwise, you may need to update your database manually.', [':url' => Url::fromRoute('<front>')->setOption('base_url', $base_url)->toString(TRUE)->getGeneratedUrl()]) . ' ' . $log_message . '</p>'; | |
410 } | |
411 else { | |
412 $last = reset($_SESSION['updates_remaining']); | |
413 list($module, $version) = array_pop($last); | |
414 $message = '<p class="error">' . $this->t('The update process was aborted prematurely while running <strong>update #@version in @module.module</strong>.', [ | |
415 '@version' => $version, | |
416 '@module' => $module, | |
417 ]) . ' ' . $log_message; | |
418 if ($dblog_exists) { | |
419 $message .= ' ' . $this->t('You may need to check the <code>watchdog</code> database table manually.'); | |
420 } | |
421 $message .= '</p>'; | |
422 } | |
423 | |
424 if (Settings::get('update_free_access')) { | |
425 $message .= '<p>' . $this->t("<strong>Reminder: don't forget to set the <code>\$settings['update_free_access']</code> value in your <code>settings.php</code> file back to <code>FALSE</code>.</strong>") . '</p>'; | |
426 } | |
427 | |
428 $build['message'] = [ | |
429 '#markup' => $message, | |
430 ]; | |
431 $build['links'] = [ | |
432 '#theme' => 'links', | |
433 '#links' => $this->helpfulLinks($request), | |
434 ]; | |
435 | |
436 // Output a list of info messages. | |
437 if (!empty($_SESSION['update_results'])) { | |
438 $all_messages = []; | |
439 foreach ($_SESSION['update_results'] as $module => $updates) { | |
440 if ($module != '#abort') { | |
441 $module_has_message = FALSE; | |
442 $info_messages = []; | |
443 foreach ($updates as $name => $queries) { | |
444 $messages = []; | |
445 foreach ($queries as $query) { | |
446 // If there is no message for this update, don't show anything. | |
447 if (empty($query['query'])) { | |
448 continue; | |
449 } | |
450 | |
451 if ($query['success']) { | |
452 $messages[] = [ | |
453 '#wrapper_attributes' => ['class' => ['success']], | |
454 '#markup' => $query['query'], | |
455 ]; | |
456 } | |
457 else { | |
458 $messages[] = [ | |
459 '#wrapper_attributes' => ['class' => ['failure']], | |
460 '#markup' => '<strong>' . $this->t('Failed:') . '</strong> ' . $query['query'], | |
461 ]; | |
462 } | |
463 } | |
464 | |
465 if ($messages) { | |
466 $module_has_message = TRUE; | |
467 if (is_numeric($name)) { | |
468 $title = $this->t('Update #@count', ['@count' => $name]); | |
469 } | |
470 else { | |
471 $title = $this->t('Update @name', ['@name' => trim($name, '_')]); | |
472 } | |
473 $info_messages[] = [ | |
474 '#theme' => 'item_list', | |
475 '#items' => $messages, | |
476 '#title' => $title, | |
477 ]; | |
478 } | |
479 } | |
480 | |
481 // If there were any messages then prefix them with the module name | |
482 // and add it to the global message list. | |
483 if ($module_has_message) { | |
484 $all_messages[] = [ | |
485 '#type' => 'container', | |
486 '#prefix' => '<h3>' . $this->t('@module module', ['@module' => $module]) . '</h3>', | |
487 '#children' => $info_messages, | |
488 ]; | |
489 } | |
490 } | |
491 } | |
492 if ($all_messages) { | |
493 $build['query_messages'] = [ | |
494 '#type' => 'container', | |
495 '#children' => $all_messages, | |
496 '#attributes' => ['class' => ['update-results']], | |
497 '#prefix' => '<h2>' . $this->t('The following updates returned messages:') . '</h2>', | |
498 ]; | |
499 } | |
500 } | |
501 unset($_SESSION['update_results']); | |
502 unset($_SESSION['update_success']); | |
503 unset($_SESSION['update_ignore_warnings']); | |
504 | |
505 return $build; | |
506 } | |
507 | |
508 /** | |
509 * Renders a list of requirement errors or warnings. | |
510 * | |
511 * @param \Symfony\Component\HttpFoundation\Request $request | |
512 * The current request. | |
513 * | |
514 * @return array | |
515 * A render array. | |
516 */ | |
517 public function requirements($severity, array $requirements, Request $request) { | |
518 $options = $severity == REQUIREMENT_WARNING ? ['continue' => 1] : []; | |
519 // @todo Revisit once https://www.drupal.org/node/2548095 is in. Something | |
520 // like Url::fromRoute('system.db_update')->setOptions() should then be | |
521 // possible. | |
522 $try_again_url = Url::fromUri($request->getUriForPath(''))->setOptions(['query' => $options])->toString(TRUE)->getGeneratedUrl(); | |
523 | |
524 $build['status_report'] = [ | |
525 '#type' => 'status_report', | |
526 '#requirements' => $requirements, | |
527 '#suffix' => $this->t('Check the messages and <a href=":url">try again</a>.', [':url' => $try_again_url]) | |
528 ]; | |
529 | |
530 $build['#title'] = $this->t('Requirements problem'); | |
531 return $build; | |
532 } | |
533 | |
534 /** | |
535 * Provides the update task list render array. | |
536 * | |
537 * @param string $active | |
538 * The active task. | |
539 * Can be one of 'requirements', 'info', 'selection', 'run', 'results'. | |
540 * | |
541 * @return array | |
542 * A render array. | |
543 */ | |
544 protected function updateTasksList($active = NULL) { | |
545 // Default list of tasks. | |
546 $tasks = [ | |
547 'requirements' => $this->t('Verify requirements'), | |
548 'info' => $this->t('Overview'), | |
549 'selection' => $this->t('Review updates'), | |
550 'run' => $this->t('Run updates'), | |
551 'results' => $this->t('Review log'), | |
552 ]; | |
553 | |
554 $task_list = [ | |
555 '#theme' => 'maintenance_task_list', | |
556 '#items' => $tasks, | |
557 '#active' => $active, | |
558 ]; | |
559 return $task_list; | |
560 } | |
561 | |
562 /** | |
563 * Starts the database update batch process. | |
564 * | |
565 * @param \Symfony\Component\HttpFoundation\Request $request | |
566 * The current request object. | |
567 */ | |
568 protected function triggerBatch(Request $request) { | |
569 $maintenance_mode = $this->state->get('system.maintenance_mode', FALSE); | |
570 // Store the current maintenance mode status in the session so that it can | |
571 // be restored at the end of the batch. | |
572 $_SESSION['maintenance_mode'] = $maintenance_mode; | |
573 // During the update, always put the site into maintenance mode so that | |
574 // in-progress schema changes do not affect visiting users. | |
575 if (empty($maintenance_mode)) { | |
576 $this->state->set('system.maintenance_mode', TRUE); | |
577 } | |
578 | |
579 $operations = []; | |
580 | |
581 // Resolve any update dependencies to determine the actual updates that will | |
582 // be run and the order they will be run in. | |
583 $start = $this->getModuleUpdates(); | |
584 $updates = update_resolve_dependencies($start); | |
585 | |
586 // Store the dependencies for each update function in an array which the | |
587 // batch API can pass in to the batch operation each time it is called. (We | |
588 // do not store the entire update dependency array here because it is | |
589 // potentially very large.) | |
590 $dependency_map = []; | |
591 foreach ($updates as $function => $update) { | |
592 $dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : []; | |
593 } | |
594 | |
595 // Determine updates to be performed. | |
596 foreach ($updates as $function => $update) { | |
597 if ($update['allowed']) { | |
598 // Set the installed version of each module so updates will start at the | |
599 // correct place. (The updates are already sorted, so we can simply base | |
600 // this on the first one we come across in the above foreach loop.) | |
601 if (isset($start[$update['module']])) { | |
602 drupal_set_installed_schema_version($update['module'], $update['number'] - 1); | |
603 unset($start[$update['module']]); | |
604 } | |
605 $operations[] = ['update_do_one', [$update['module'], $update['number'], $dependency_map[$function]]]; | |
606 } | |
607 } | |
608 | |
609 $post_updates = $this->postUpdateRegistry->getPendingUpdateFunctions(); | |
610 | |
611 if ($post_updates) { | |
612 // Now we rebuild all caches and after that execute the hook_post_update() | |
613 // functions. | |
614 $operations[] = ['drupal_flush_all_caches', []]; | |
615 foreach ($post_updates as $function) { | |
616 $operations[] = ['update_invoke_post_update', [$function]]; | |
617 } | |
618 } | |
619 | |
620 $batch['operations'] = $operations; | |
621 $batch += [ | |
622 'title' => $this->t('Updating'), | |
623 'init_message' => $this->t('Starting updates'), | |
624 'error_message' => $this->t('An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.'), | |
625 'finished' => ['\Drupal\system\Controller\DbUpdateController', 'batchFinished'], | |
626 ]; | |
627 batch_set($batch); | |
628 | |
629 // @todo Revisit once https://www.drupal.org/node/2548095 is in. | |
630 return batch_process(Url::fromUri('base://results'), Url::fromUri('base://start')); | |
631 } | |
632 | |
633 /** | |
634 * Finishes the update process and stores the results for eventual display. | |
635 * | |
636 * After the updates run, all caches are flushed. The update results are | |
637 * stored into the session (for example, to be displayed on the update results | |
638 * page in update.php). Additionally, if the site was off-line, now that the | |
639 * update process is completed, the site is set back online. | |
640 * | |
641 * @param $success | |
642 * Indicate that the batch API tasks were all completed successfully. | |
643 * @param array $results | |
644 * An array of all the results that were updated in update_do_one(). | |
645 * @param array $operations | |
646 * A list of all the operations that had not been completed by the batch API. | |
647 */ | |
648 public static function batchFinished($success, $results, $operations) { | |
649 // No updates to run, so caches won't get flushed later. Clear them now. | |
650 drupal_flush_all_caches(); | |
651 | |
652 $_SESSION['update_results'] = $results; | |
653 $_SESSION['update_success'] = $success; | |
654 $_SESSION['updates_remaining'] = $operations; | |
655 | |
656 // Now that the update is done, we can put the site back online if it was | |
657 // previously not in maintenance mode. | |
658 if (empty($_SESSION['maintenance_mode'])) { | |
659 \Drupal::state()->set('system.maintenance_mode', FALSE); | |
660 } | |
661 unset($_SESSION['maintenance_mode']); | |
662 } | |
663 | |
664 /** | |
665 * Provides links to the homepage and administration pages. | |
666 * | |
667 * @param \Symfony\Component\HttpFoundation\Request $request | |
668 * The current request. | |
669 * | |
670 * @return array | |
671 * An array of links. | |
672 */ | |
673 protected function helpfulLinks(Request $request) { | |
674 // @todo Simplify with https://www.drupal.org/node/2548095 | |
675 $base_url = str_replace('/update.php', '', $request->getBaseUrl()); | |
676 $links['front'] = [ | |
677 'title' => $this->t('Front page'), | |
678 'url' => Url::fromRoute('<front>')->setOption('base_url', $base_url), | |
679 ]; | |
680 if ($this->account->hasPermission('access administration pages')) { | |
681 $links['admin-pages'] = [ | |
682 'title' => $this->t('Administration pages'), | |
683 'url' => Url::fromRoute('system.admin')->setOption('base_url', $base_url), | |
684 ]; | |
685 } | |
686 return $links; | |
687 } | |
688 | |
689 /** | |
690 * Retrieves module updates. | |
691 * | |
692 * @return array | |
693 * The module updates that can be performed. | |
694 */ | |
695 protected function getModuleUpdates() { | |
696 $return = []; | |
697 $updates = update_get_update_list(); | |
698 foreach ($updates as $module => $update) { | |
699 $return[$module] = $update['start']; | |
700 } | |
701 | |
702 return $return; | |
703 } | |
704 | |
705 } |