DbUpdateController.php 25 KB

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