root = $root; $this->keyValueExpirableFactory = $key_value_expirable_factory; $this->cache = $cache; $this->state = $state; $this->moduleHandler = $module_handler; $this->account = $account; $this->bareHtmlPageRenderer = $bare_html_page_renderer; $this->postUpdateRegistry = $post_update_registry; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { return new static( $container->get('app.root'), $container->get('keyvalue.expirable'), $container->get('cache.default'), $container->get('state'), $container->get('module_handler'), $container->get('current_user'), $container->get('bare_html_page_renderer'), $container->get('update.post_update_registry') ); } /** * Returns a database update page. * * @param string $op * The update operation to perform. Can be any of the below: * - info * - selection * - run * - results * @param \Symfony\Component\HttpFoundation\Request $request * The current request object. * * @return \Symfony\Component\HttpFoundation\Response * A response object object. */ public function handle($op, Request $request) { require_once $this->root . '/core/includes/install.inc'; require_once $this->root . '/core/includes/update.inc'; drupal_load_updates(); update_fix_compatibility(); if ($request->query->get('continue')) { $_SESSION['update_ignore_warnings'] = TRUE; } $regions = []; $requirements = update_check_requirements(); $severity = drupal_requirements_severity($requirements); if ($severity == REQUIREMENT_ERROR || ($severity == REQUIREMENT_WARNING && empty($_SESSION['update_ignore_warnings']))) { $regions['sidebar_first'] = $this->updateTasksList('requirements'); $output = $this->requirements($severity, $requirements, $request); } else { switch ($op) { case 'selection': $regions['sidebar_first'] = $this->updateTasksList('selection'); $output = $this->selection($request); break; case 'run': $regions['sidebar_first'] = $this->updateTasksList('run'); $output = $this->triggerBatch($request); break; case 'info': $regions['sidebar_first'] = $this->updateTasksList('info'); $output = $this->info($request); break; case 'results': $regions['sidebar_first'] = $this->updateTasksList('results'); $output = $this->results($request); break; // Regular batch ops : defer to batch processing API. default: require_once $this->root . '/core/includes/batch.inc'; $regions['sidebar_first'] = $this->updateTasksList('run'); $output = _batch_page($request); break; } } if ($output instanceof Response) { return $output; } $title = isset($output['#title']) ? $output['#title'] : $this->t('Drupal database update'); return $this->bareHtmlPageRenderer->renderBarePage($output, $title, 'maintenance_page', $regions); } /** * Returns the info database update page. * * @param \Symfony\Component\HttpFoundation\Request $request * The current request. * * @return array * A render array. */ protected function info(Request $request) { // Change query-strings on css/js files to enforce reload for all users. _drupal_flush_css_js(); // Flush the cache of all data for the update status module. $this->keyValueExpirableFactory->get('update')->deleteAll(); $this->keyValueExpirableFactory->get('update_available_release')->deleteAll(); $build['info_header'] = [ '#markup' => '
' . $this->t('Use this utility to update your database whenever a new release of Drupal or a module is installed.') . '
' . $this->t('For more detailed information, see the upgrading handbook. If you are unsure what these terms mean you should probably contact your hosting provider.') . '
', ]; $info[] = $this->t("Back up your code. 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."); $info[] = $this->t('Put your site into maintenance mode.', [ ':url' => Url::fromRoute('system.site_maintenance_mode')->toString(TRUE)->getGeneratedUrl(), ]); $info[] = $this->t('Back up your database. This process will change your database values and in case of emergency you may need to revert to a backup.'); $info[] = $this->t('Install your new files in the appropriate location, as described in the handbook.'); $build['info'] = [ '#theme' => 'item_list', '#list_type' => 'ol', '#items' => $info, ]; $build['info_footer'] = [ '#markup' => '' . $this->t('When you have performed the steps above, you may proceed.') . '
', ]; $build['link'] = [ '#type' => 'link', '#title' => $this->t('Continue'), '#attributes' => ['class' => ['button', 'button--primary']], // @todo Revisit once https://www.drupal.org/node/2548095 is in. '#url' => Url::fromUri('base://selection'), ]; return $build; } /** * Renders a list of available database updates. * * @param \Symfony\Component\HttpFoundation\Request $request * The current request. * * @return array * A render array. */ protected function selection(Request $request) { // Make sure there is no stale theme registry. $this->cache->deleteAll(); $count = 0; $incompatible_count = 0; $build['start'] = [ '#tree' => TRUE, '#type' => 'details', ]; // Ensure system.module's updates appear first. $build['start']['system'] = []; $starting_updates = []; $incompatible_updates_exist = FALSE; $updates_per_module = []; foreach (['update', 'post_update'] as $update_type) { switch ($update_type) { case 'update': $updates = update_get_update_list(); break; case 'post_update': $updates = $this->postUpdateRegistry->getPendingUpdateInformation(); break; } foreach ($updates as $module => $update) { if (!isset($update['start'])) { $build['start'][$module] = [ '#type' => 'item', '#title' => $module . ' module', '#markup' => $update['warning'], '#prefix' => ' ', ]; $incompatible_updates_exist = TRUE; continue; } if (!empty($update['pending'])) { $updates_per_module += [$module => []]; $updates_per_module[$module] = array_merge($updates_per_module[$module], $update['pending']); $build['start'][$module] = [ '#type' => 'hidden', '#value' => $update['start'], ]; // Store the previous items in order to merge normal updates and // post_update functions together. $build['start'][$module] = [ '#theme' => 'item_list', '#items' => $updates_per_module[$module], '#title' => $module . ' module', ]; if ($update_type === 'update') { $starting_updates[$module] = $update['start']; } } if (isset($update['pending'])) { $count = $count + count($update['pending']); } } } // Find and label any incompatible updates. foreach (update_resolve_dependencies($starting_updates) as $data) { if (!$data['allowed']) { $incompatible_updates_exist = TRUE; $incompatible_count++; $module_update_key = $data['module'] . '_updates'; if (isset($build['start'][$module_update_key]['#items'][$data['number']])) { if ($data['missing_dependencies']) { $text = $this->t('This update will been skipped due to the following missing dependencies:') . '' . implode(', ', $data['missing_dependencies']) . ''; } else { $text = $this->t("This update will be skipped due to an error in the module's code."); } $build['start'][$module_update_key]['#items'][$data['number']] .= '' . $this->t('The version of Drupal you are updating from has been automatically detected.') . '
', '#weight' => -5, ]; if ($incompatible_count) { $build['start']['#title'] = $this->formatPlural( $count, '1 pending update (@number_applied to be applied, @number_incompatible skipped)', '@count pending updates (@number_applied to be applied, @number_incompatible skipped)', ['@number_applied' => $count - $incompatible_count, '@number_incompatible' => $incompatible_count] ); } else { $build['start']['#title'] = $this->formatPlural($count, '1 pending update', '@count pending updates'); } // @todo Simplify with https://www.drupal.org/node/2548095 $base_url = str_replace('/update.php', '', $request->getBaseUrl()); $url = (new Url('system.db_update', ['op' => 'run']))->setOption('base_url', $base_url); $build['link'] = [ '#type' => 'link', '#title' => $this->t('Apply pending updates'), '#attributes' => ['class' => ['button', 'button--primary']], '#weight' => 5, '#url' => $url, '#access' => $url->access($this->currentUser()), ]; } return $build; } /** * Displays results of the update script with any accompanying errors. * * @param \Symfony\Component\HttpFoundation\Request $request * The current request. * * @return array * A render array. */ protected function results(Request $request) { // @todo Simplify with https://www.drupal.org/node/2548095 $base_url = str_replace('/update.php', '', $request->getBaseUrl()); // Report end result. $dblog_exists = $this->moduleHandler->moduleExists('dblog'); if ($dblog_exists && $this->account->hasPermission('access site reports')) { $log_message = $this->t('All errors have been logged.', [ ':url' => Url::fromRoute('dblog.overview')->setOption('base_url', $base_url)->toString(TRUE)->getGeneratedUrl(), ]); } else { $log_message = $this->t('All errors have been logged.'); } if (!empty($_SESSION['update_success'])) { $message = '' . $this->t('Updates were attempted. If you see no failures below, you may proceed happily back to your site. Otherwise, you may need to update your database manually.', [':url' => Url::fromRoute('
' . $this->t('The update process was aborted prematurely while running update #@version in @module.module.', [
'@version' => $version,
'@module' => $module,
]) . ' ' . $log_message;
if ($dblog_exists) {
$message .= ' ' . $this->t('You may need to check the watchdog
database table manually.');
}
$message .= '
' . $this->t("Reminder: don't forget to set the \$settings['update_free_access']
value in your settings.php
file back to FALSE
.") . '