diff --git a/.htaccess b/.htaccess
index 5ca73051447c..c32b18211985 100644
--- a/.htaccess
+++ b/.htaccess
@@ -113,7 +113,6 @@ DirectoryIndex index.php index.html index.htm
# RewriteBase /
# Redirect common PHP files to their new locations.
- RewriteCond %{REQUEST_URI} ^(.*)?/(update.php) [OR]
RewriteCond %{REQUEST_URI} ^(.*)?/(install.php) [OR]
RewriteCond %{REQUEST_URI} ^(.*)?/(rebuild.php)
RewriteCond %{REQUEST_URI} !core
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index d811f4d27021..278677497595 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -1574,6 +1574,47 @@ function template_preprocess_container(&$variables) {
$variables['attributes'] = $element['#attributes'];
}
+
+/**
+ * Returns HTML for a list of maintenance tasks to perform.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - items: An associative array of maintenance tasks.
+ * It's the caller's responsibility to ensure this array's items contain no
+ * dangerous HTML such as SCRIPT tags.
+ * - active: The key for the currently active maintenance task.
+ *
+ * @ingroup themeable
+ */
+function theme_task_list($variables) {
+ $items = $variables['items'];
+ $active = $variables['active'];
+
+ $done = isset($items[$active]) || $active == NULL;
+ $output = '
Installation tasks
';
+ $output .= '';
+
+ foreach ($items as $k => $item) {
+ if ($active == $k) {
+ $class = 'active';
+ $status = '(' . t('active') . ')';
+ $done = FALSE;
+ }
+ else {
+ $class = $done ? 'done' : '';
+ $status = $done ? '(' . t('done') . ')' : '';
+ }
+ $output .= '- ';
+ $output .= $item;
+ $output .= ($status ? ' ' . $status . '' : '');
+ $output .= '
';
+ }
+ $output .= '
';
+ return $output;
+}
+
/**
* @} End of "addtogroup themeable".
*/
diff --git a/core/includes/theme.maintenance.inc b/core/includes/theme.maintenance.inc
index 2ceec4a5d198..0a9fb5aaba31 100644
--- a/core/includes/theme.maintenance.inc
+++ b/core/includes/theme.maintenance.inc
@@ -103,46 +103,6 @@ function _drupal_maintenance_theme() {
Drupal::service('theme.registry');
}
-/**
- * Returns HTML for a list of maintenance tasks to perform.
- *
- * @param $variables
- * An associative array containing:
- * - items: An associative array of maintenance tasks.
- * It's the caller's responsibility to ensure this array's items contain no
- * dangerous HTML such as SCRIPT tags.
- * - active: The key for the currently active maintenance task.
- *
- * @ingroup themeable
- */
-function theme_task_list($variables) {
- $items = $variables['items'];
- $active = $variables['active'];
-
- $done = isset($items[$active]) || $active == NULL;
- $output = 'Installation tasks
';
- $output .= '';
-
- foreach ($items as $k => $item) {
- if ($active == $k) {
- $class = 'active';
- $status = '(' . t('active') . ')';
- $done = FALSE;
- }
- else {
- $class = $done ? 'done' : '';
- $status = $done ? '(' . t('done') . ')' : '';
- }
- $output .= '- ';
- $output .= $item;
- $output .= ($status ? ' ' . $status . '' : '');
- $output .= '
';
- }
- $output .= '
';
- return $output;
-}
-
/**
* Returns HTML for a results report of an operation run by authorize.php.
*
diff --git a/core/includes/update.inc b/core/includes/update.inc
index 945ffe750e30..f9a88db9f55a 100644
--- a/core/includes/update.inc
+++ b/core/includes/update.inc
@@ -131,33 +131,13 @@ function update_system_schema_requirements() {
/**
* Checks update requirements and reports errors and (optionally) warnings.
- *
- * @param $skip_warnings
- * (optional) If set to TRUE, requirement warnings will be ignored, and a
- * report will only be issued if there are requirement errors. Defaults to
- * FALSE.
*/
-function update_check_requirements($skip_warnings = FALSE) {
+function update_check_requirements() {
// Check requirements of all loaded modules.
$requirements = \Drupal::moduleHandler()->invokeAll('requirements', array('update'));
$requirements += update_system_schema_requirements();
$requirements += update_settings_file_requirements();
- $severity = drupal_requirements_severity($requirements);
-
- // If there are errors, always display them. If there are only warnings, skip
- // them if the caller has indicated they should be skipped.
- if ($severity == REQUIREMENT_ERROR || ($severity == REQUIREMENT_WARNING && !$skip_warnings)) {
- $regions['sidebar_first'] = update_task_list('requirements');
- $status_report = array(
- '#theme' => 'status_report',
- '#requirements' => $requirements,
- );
- $status_report['#suffix'] = 'Check the messages and try again.';
-
- drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
- print DefaultHtmlPageRenderer::renderPage($status_report, 'Requirements problem', 'maintenance', $regions);
- exit();
- }
+ return $requirements;
}
/**
@@ -277,114 +257,6 @@ function update_do_one($module, $number, $dependency_map, &$context) {
$context['message'] = 'Updating ' . String::checkPlain($module) . ' module';
}
-/**
- * Starts the database update batch process.
- *
- * @param $start
- * An array whose keys contain the names of modules to be updated during the
- * current batch process, and whose values contain the number of the first
- * requested update for that module. The actual updates that are run (and the
- * order they are run in) will depend on the results of passing this data
- * through the update dependency system.
- * @param $redirect
- * Path to redirect to when the batch has finished processing.
- * @param $url
- * URL of the batch processing page (should only be used for separate
- * scripts like update.php).
- * @param $batch
- * Optional parameters to pass into the batch API.
- * @param $redirect_callback
- * (optional) Specify a function to be called to redirect to the progressive
- * processing page.
- *
- * @see update_resolve_dependencies()
- */
-function update_batch($start, $redirect = NULL, $url = NULL, $batch = array(), $redirect_callback = NULL) {
- // During the update, bring the site offline so that schema changes do not
- // affect visiting users.
- $maintenance_mode = \Drupal::config('system.maintenance')->get('enabled');
- if (isset($maintenance_mode)) {
- $_SESSION['maintenance_mode'] = $maintenance_mode;
- }
- if (empty($_SESSION['maintenance_mode'])) {
- if (db_table_exists('state')) {
- \Drupal::state()->set('system.maintenance_mode', TRUE);
- }
- }
-
- // Resolve any update dependencies to determine the actual updates that will
- // be run and the order they will be run in.
- $updates = update_resolve_dependencies($start);
-
- // Store the dependencies for each update function in an array which the
- // batch API can pass in to the batch operation each time it is called. (We
- // do not store the entire update dependency array here because it is
- // potentially very large.)
- $dependency_map = array();
- foreach ($updates as $function => $update) {
- $dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : array();
- }
-
- $operations = array();
- foreach ($updates as $update) {
- if ($update['allowed']) {
- // Set the installed version of each module so updates will start at the
- // correct place. (The updates are already sorted, so we can simply base
- // this on the first one we come across in the above foreach loop.)
- if (isset($start[$update['module']])) {
- drupal_set_installed_schema_version($update['module'], $update['number'] - 1);
- unset($start[$update['module']]);
- }
- // Add this update function to the batch.
- $function = $update['module'] . '_update_' . $update['number'];
- $operations[] = array('update_do_one', array($update['module'], $update['number'], $dependency_map[$function]));
- }
- }
- $batch['operations'] = $operations;
- $batch += array(
- 'title' => 'Updating',
- 'init_message' => 'Starting updates',
- 'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.',
- 'finished' => 'update_finished',
- 'file' => 'core/includes/update.inc',
- );
- batch_set($batch);
- return batch_process($redirect, $url, $redirect_callback);
-}
-
-/**
- * Finishes the update process and stores the results for eventual display.
- *
- * After the updates run, all caches are flushed. The update results are
- * stored into the session (for example, to be displayed on the update results
- * page in update.php). Additionally, if the site was off-line, now that the
- * update process is completed, the site is set back online.
- *
- * @param $success
- * Indicate that the batch API tasks were all completed successfully.
- * @param $results
- * An array of all the results that were updated in update_do_one().
- * @param $operations
- * A list of all the operations that had not been completed by the batch API.
- *
- * @see update_batch()
- */
-function update_finished($success, $results, $operations) {
- // Clear the caches in case the data has been updated.
- update_flush_all_caches();
-
- $_SESSION['update_results'] = $results;
- $_SESSION['update_success'] = $success;
- $_SESSION['updates_remaining'] = $operations;
-
- // Now that the update is done, we can put the site back online if it was
- // previously in maintenance mode.
- if (isset($_SESSION['maintenance_mode'])) {
- \Drupal::state()->set('system.maintenance_mode', FALSE);
- unset($_SESSION['maintenance_mode']);
- }
-}
-
/**
* Returns a list of all the pending database updates.
*
diff --git a/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php b/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php
deleted file mode 100644
index d8a40aa4a913..000000000000
--- a/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php
+++ /dev/null
@@ -1,66 +0,0 @@
-register('lock', 'Drupal\Core\Lock\NullLockBackend');
-
- // Prevent config from being accessed via a cache wrapper by removing
- // any existing definition and setting an alias to the actual storage.
- $container->removeDefinition('config.storage');
- $container->setAlias('config.storage', 'config.storage.active');
-
- $container
- ->register('cache_factory', 'Drupal\Core\Cache\MemoryBackendFactory');
- $container
- ->register('router.builder', 'Drupal\Core\Routing\RouteBuilderStatic');
-
- $container->register('theme_handler', 'Drupal\Core\Extension\ThemeHandler')
- ->addArgument(new Reference('config.factory'))
- ->addArgument(new Reference('module_handler'))
- ->addArgument(new Reference('state'))
- ->addArgument(new Reference('info_parser'))
- ->addArgument(new Reference('logger.channel.default'))
- ->addArgument(new Reference('asset.css.collection_optimizer'));
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function alter(ContainerBuilder $container) {
- // Ensure that URLs generated for the home and admin pages don't have
- // 'update.php' in them.
- $request = Request::createFromGlobals();
- $definition = $container->getDefinition('url_generator');
- $definition->addMethodCall('setBasePath', array(str_replace('/core', '', $request->getBasePath()) . '/'));
- // We need to set the script path to an empty string since the value
- // determined by \Drupal\Core\Routing\UrlGenerator::setRequest() is invalid
- // once '/core' has been removed from the base path.
- $definition->addMethodCall('setScriptPath', array(''));
- }
-
-}
diff --git a/core/lib/Drupal/Core/Update/Form/UpdateScriptSelectionForm.php b/core/lib/Drupal/Core/Update/Form/UpdateScriptSelectionForm.php
deleted file mode 100644
index 574ec6c46db7..000000000000
--- a/core/lib/Drupal/Core/Update/Form/UpdateScriptSelectionForm.php
+++ /dev/null
@@ -1,134 +0,0 @@
- TRUE,
- '#type' => 'details',
- );
-
- // Ensure system.module's updates appear first.
- $form['start']['system'] = array();
-
- $updates = update_get_update_list();
- $starting_updates = array();
- $incompatible_updates_exist = FALSE;
- foreach ($updates as $module => $update) {
- if (!isset($update['start'])) {
- $form['start'][$module] = array(
- '#type' => 'item',
- '#title' => $module . ' module',
- '#markup' => $update['warning'],
- '#prefix' => '',
- '#suffix' => '
',
- );
- $incompatible_updates_exist = TRUE;
- continue;
- }
- if (!empty($update['pending'])) {
- $starting_updates[$module] = $update['start'];
- $form['start'][$module] = array(
- '#type' => 'hidden',
- '#value' => $update['start'],
- );
- $form['start'][$module . '_updates'] = array(
- '#theme' => 'item_list',
- '#items' => $update['pending'],
- '#title' => $module . ' module',
- );
- }
- 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($form['start'][$module_update_key]['#items'][$data['number']])) {
- $text = $data['missing_dependencies'] ? 'This update will been skipped due to the following missing dependencies: ' . implode(', ', $data['missing_dependencies']) . '' : "This update will be skipped due to an error in the module's code.";
- $form['start'][$module_update_key]['#items'][$data['number']] .= '' . $text . '
';
- }
- // Move the module containing this update to the top of the list.
- $form['start'] = array($module_update_key => $form['start'][$module_update_key]) + $form['start'];
- }
- }
-
- // Warn the user if any updates were incompatible.
- if ($incompatible_updates_exist) {
- drupal_set_message('Some of the pending updates cannot be applied because their dependencies were not met.', 'warning');
- }
-
- if (empty($count)) {
- drupal_set_message(t('No pending updates.'));
- unset($form);
- $form['links'] = array(
- '#theme' => 'links',
- '#links' => update_helpful_links(),
- );
-
- // No updates to run, so caches won't get flushed later. Clear them now.
- update_flush_all_caches();
- }
- else {
- $form['help'] = array(
- '#markup' => 'The version of Drupal you are updating from has been automatically detected.
',
- '#weight' => -5,
- );
- if ($incompatible_count) {
- $form['start']['#title'] = format_plural(
- $count,
- '1 pending update (@number_applied to be applied, @number_incompatible skipped)',
- '@count pending updates (@number_applied to be applied, @number_incompatible skipped)',
- array('@number_applied' => $count - $incompatible_count, '@number_incompatible' => $incompatible_count)
- );
- }
- else {
- $form['start']['#title'] = format_plural($count, '1 pending update', '@count pending updates');
- }
- $form['actions'] = array('#type' => 'actions');
- $form['actions']['submit'] = array(
- '#type' => 'submit',
- '#value' => 'Apply pending updates',
- '#button_type' => 'primary',
- );
- }
- return $form;
- }
-
- /**
- * {@inheritdoc}
- */
- public function submitForm(array &$form, FormStateInterface $form_state) {
- }
-
-}
diff --git a/core/modules/system/src/Access/DbUpdateAccessCheck.php b/core/modules/system/src/Access/DbUpdateAccessCheck.php
new file mode 100644
index 000000000000..0d37db196370
--- /dev/null
+++ b/core/modules/system/src/Access/DbUpdateAccessCheck.php
@@ -0,0 +1,36 @@
+hasPermission('administer software updates') ? static::ALLOW : static::KILL;
+ }
+}
diff --git a/core/modules/system/src/Controller/DbUpdateController.php b/core/modules/system/src/Controller/DbUpdateController.php
new file mode 100644
index 000000000000..62752aebe015
--- /dev/null
+++ b/core/modules/system/src/Controller/DbUpdateController.php
@@ -0,0 +1,612 @@
+keyValueExpirableFactory = $key_value_expirable_factory;
+ $this->cache = $cache;
+ $this->state = $state;
+ $this->moduleHandler = $module_handler;
+ $this->account = $account;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('keyvalue.expirable'),
+ $container->get('cache.default'),
+ $container->get('state'),
+ $container->get('module_handler'),
+ $container->get('current_user')
+ );
+ }
+
+ /**
+ * 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 DRUPAL_ROOT . '/core/includes/install.inc';
+ require_once DRUPAL_ROOT . '/core/includes/update.inc';
+
+ drupal_load_updates();
+ update_fix_compatibility();
+
+ if ($request->query->get('continue')) {
+ $_SESSION['update_ignore_warnings'] = TRUE;
+ }
+
+ $regions = array();
+ $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);
+ }
+ else {
+ switch ($op) {
+ case 'selection':
+ $regions['sidebar_first'] = $this->updateTasksList('selection');
+ $output = $this->selection();
+ 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();
+ break;
+
+ case 'results':
+ $regions['sidebar_first'] = $this->updateTasksList('results');
+ $output = $this->results();
+ break;
+
+ // Regular batch ops : defer to batch processing API.
+ default:
+ require_once DRUPAL_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 new Response(DefaultHtmlPageRenderer::renderPage($output, $title, 'maintenance', $regions));
+ }
+
+ /**
+ * Returns the info database update page.
+ *
+ * @return array
+ * A render array.
+ */
+ protected function info() {
+ // 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'] = array(
+ '#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.', array(
+ '@url' => $this->url('system.site_maintenance_mode'),
+ ));
+ $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'] = array(
+ '#theme' => 'item_list',
+ '#list_type' => 'ol',
+ '#items' => $info,
+ );
+ $build['info_footer'] = array(
+ '#markup' => '' . $this->t('When you have performed the steps above, you may proceed.') . '
',
+ );
+
+ $url = new Url('system.db_update', array('op' => 'selection'));
+ $build['link'] = array(
+ '#type' => 'link',
+ '#title' => $this->t('Continue'),
+ '#attributes' => array('class' => array('button', 'button--primary')),
+ ) + $url->toRenderArray();
+ return $build;
+ }
+
+ /**
+ * Renders a list of available database updates.
+ *
+ * @return array
+ * A render array.
+ */
+ protected function selection() {
+ // Make sure there is no stale theme registry.
+ $this->cache->deleteAll();
+
+ $count = 0;
+ $incompatible_count = 0;
+ $build['start'] = array(
+ '#tree' => TRUE,
+ '#type' => 'details',
+ );
+
+ // Ensure system.module's updates appear first.
+ $build['start']['system'] = array();
+
+ $updates = update_get_update_list();
+ $starting_updates = array();
+ $incompatible_updates_exist = FALSE;
+ foreach ($updates as $module => $update) {
+ if (!isset($update['start'])) {
+ $build['start'][$module] = array(
+ '#type' => 'item',
+ '#title' => $module . ' module',
+ '#markup' => $update['warning'],
+ '#prefix' => '',
+ '#suffix' => '
',
+ );
+ $incompatible_updates_exist = TRUE;
+ continue;
+ }
+ if (!empty($update['pending'])) {
+ $starting_updates[$module] = $update['start'];
+ $build['start'][$module] = array(
+ '#type' => 'hidden',
+ '#value' => $update['start'],
+ );
+ $build['start'][$module . '_updates'] = array(
+ '#theme' => 'item_list',
+ '#items' => $update['pending'],
+ '#title' => $module . ' module',
+ );
+ }
+ 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']] .= '' . $text . '
';
+ }
+ // Move the module containing this update to the top of the list.
+ $build['start'] = array($module_update_key => $build['start'][$module_update_key]) + $build['start'];
+ }
+ }
+
+ // Warn the user if any updates were incompatible.
+ if ($incompatible_updates_exist) {
+ drupal_set_message($this->t('Some of the pending updates cannot be applied because their dependencies were not met.'), 'warning');
+ }
+
+ if (empty($count)) {
+ drupal_set_message($this->t('No pending updates.'));
+ unset($build);
+ $build['links'] = array(
+ '#theme' => 'links',
+ '#links' => $this->helpfulLinks(),
+ );
+
+ // No updates to run, so caches won't get flushed later. Clear them now.
+ drupal_flush_all_caches();
+ }
+ else {
+ $build['help'] = array(
+ '#markup' => '' . $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)',
+ array('@number_applied' => $count - $incompatible_count, '@number_incompatible' => $incompatible_count)
+ );
+ }
+ else {
+ $build['start']['#title'] = $this->formatPlural($count, '1 pending update', '@count pending updates');
+ }
+ $url = new Url('system.db_update', array('op' => 'run'));
+ $build['link'] = array(
+ '#type' => 'link',
+ '#title' => $this->t('Apply pending updates'),
+ '#attributes' => array('class' => array('button', 'button--primary')),
+ '#weight' => 5,
+ ) + $url->toRenderArray();
+ }
+
+ return $build;
+ }
+
+ /**
+ * Displays results of the update script with any accompanying errors.
+ *
+ * @return array
+ * A render array.
+ */
+ protected function results() {
+ // 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.', array(
+ '@url' => $this->url('dblog.overview'),
+ ));
+ }
+ 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.', array('@url' => $this->url(''))) . ' ' . $log_message . '
';
+ }
+ else {
+ $last = reset($_SESSION['updates_remaining']);
+ list($module, $version) = array_pop($last);
+ $message = '' . $this->t('The update process was aborted prematurely while running update #@version in @module.module.', array(
+ '@version' => $version,
+ '@module' => $module,
+ )) . ' ' . $log_message;
+ if ($dblog_exists) {
+ $message .= ' ' . $this->t('You may need to check the watchdog database table manually.');
+ }
+ $message .= '
';
+ }
+
+ if (Settings::get('update_free_access')) {
+ $message .= '' . $this->t("Reminder: don't forget to set the \$settings['update_free_access'] value in your settings.php file back to FALSE.") . '
';
+ }
+
+ $build['message'] = array(
+ '#markup' => $message,
+ );
+ $build['links'] = array(
+ '#theme' => 'links',
+ '#links' => $this->helpfulLinks(),
+ );
+
+ // Output a list of info messages.
+ if (!empty($_SESSION['update_results'])) {
+ $all_messages = array();
+ foreach ($_SESSION['update_results'] as $module => $updates) {
+ if ($module != '#abort') {
+ $module_has_message = FALSE;
+ $info_messages = array();
+ foreach ($updates as $number => $queries) {
+ $messages = array();
+ foreach ($queries as $query) {
+ // If there is no message for this update, don't show anything.
+ if (empty($query['query'])) {
+ continue;
+ }
+
+ if ($query['success']) {
+ $messages[] = array(
+ '#wrapper_attributes' => array('class' => array('success')),
+ '#markup' => $query['query'],
+ );
+ }
+ else {
+ $messages[] = array(
+ '#wrapper_attributes' => array('class' => array('failure')),
+ '#markup' => '' . $this->t('Failed:') . ' ' . $query['query'],
+ );
+ }
+ }
+
+ if ($messages) {
+ $module_has_message = TRUE;
+ $info_messages[] = array(
+ '#theme' => 'item_list',
+ '#items' => $messages,
+ '#title' => $this->t('Update #@count', array('@count' => $number)),
+ );
+ }
+ }
+
+ // If there were any messages then prefix them with the module name
+ // and add it to the global message list.
+ if ($module_has_message) {
+ $all_messages[] = array(
+ '#type' => 'container',
+ '#prefix' => '' . $this->t('@module module', array('@module' => $module)) . '
',
+ '#children' => $info_messages,
+ );
+ }
+ }
+ }
+ if ($all_messages) {
+ $build['query_messsages'] = array(
+ '#type' => 'container',
+ '#children' => $all_messages,
+ '#attributes' => array('class' => array('update-results')),
+ '#prefix' => '' . $this->t('The following updates returned messages:') . '
',
+ );
+ }
+ }
+ unset($_SESSION['update_results']);
+ unset($_SESSION['update_success']);
+ unset($_SESSION['update_ignore_warnings']);
+
+ return $build;
+ }
+
+ /**
+ * Renders a list of requirement errors or warnings.
+ *
+ * @return array
+ * A render array.
+ */
+ public function requirements($severity, array $requirements) {
+ $options = $severity == REQUIREMENT_WARNING ? array('continue' => 1) : array();
+ $try_again_url = $this->url('system.db_update', $options);
+
+ $build['status_report'] = array(
+ '#theme' => 'status_report',
+ '#requirements' => $requirements,
+ '#suffix' => $this->t('Check the messages and try again.', array('@url' => $try_again_url))
+ );
+
+ $build['#title'] = $this->t('Requirements problem');
+ return $build;
+ }
+
+ /**
+ * Provides the update task list render array.
+ *
+ * @param string $active
+ * The active task.
+ * Can be one of 'requirements', 'info', 'selection', 'run', 'results'.
+ *
+ * @return array
+ * A render array.
+ */
+ protected function updateTasksList($active = NULL) {
+ // Default list of tasks.
+ $tasks = array(
+ 'requirements' => $this->t('Verify requirements'),
+ 'info' => $this->t('Overview'),
+ 'selection' => $this->t('Review updates'),
+ 'run' => $this->t('Run updates'),
+ 'results' => $this->t('Review log'),
+ );
+
+ $task_list = array(
+ '#theme' => 'task_list',
+ '#items' => $tasks,
+ '#active' => $active,
+ );
+ return $task_list;
+ }
+
+ /**
+ * Starts the database update batch process.
+ *
+ * @param \Symfony\Component\HttpFoundation\Request $request
+ * The current request object.
+ */
+ protected function triggerBatch(Request $request) {
+ // During the update, bring the site offline so that schema changes do not
+ // affect visiting users.
+ $maintenance_mode = $this->config('system.maintenance')->get('enabled');
+ if (isset($maintenance_mode)) {
+ $_SESSION['maintenance_mode'] = $maintenance_mode;
+ }
+ if (empty($_SESSION['maintenance_mode'])) {
+ $this->state->set('system.maintenance_mode', TRUE);
+ }
+
+ $start = $this->getModuleUpdates();
+ // Resolve any update dependencies to determine the actual updates that will
+ // be run and the order they will be run in.
+ $updates = update_resolve_dependencies($start);
+
+ // Store the dependencies for each update function in an array which the
+ // batch API can pass in to the batch operation each time it is called. (We
+ // do not store the entire update dependency array here because it is
+ // potentially very large.)
+ $dependency_map = array();
+ foreach ($updates as $function => $update) {
+ $dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : array();
+ }
+
+ $operations = array();
+ foreach ($updates as $update) {
+ if ($update['allowed']) {
+ // Set the installed version of each module so updates will start at the
+ // correct place. (The updates are already sorted, so we can simply base
+ // this on the first one we come across in the above foreach loop.)
+ if (isset($start[$update['module']])) {
+ drupal_set_installed_schema_version($update['module'], $update['number'] - 1);
+ unset($start[$update['module']]);
+ }
+ // Add this update function to the batch.
+ $function = $update['module'] . '_update_' . $update['number'];
+ $operations[] = array('update_do_one', array($update['module'], $update['number'], $dependency_map[$function]));
+ }
+ }
+ $batch['operations'] = $operations;
+ $batch += array(
+ 'title' => $this->t('Updating'),
+ 'init_message' => $this->t('Starting updates'),
+ '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.'),
+ 'finished' => array('\Drupal\system\Controller\DbUpdateController', 'batchFinished'),
+ );
+ batch_set($batch);
+
+ return batch_process('update.php/results', 'update.php/batch');
+ }
+
+ /**
+ * Finishes the update process and stores the results for eventual display.
+ *
+ * After the updates run, all caches are flushed. The update results are
+ * stored into the session (for example, to be displayed on the update results
+ * page in update.php). Additionally, if the site was off-line, now that the
+ * update process is completed, the site is set back online.
+ *
+ * @param $success
+ * Indicate that the batch API tasks were all completed successfully.
+ * @param array $results
+ * An array of all the results that were updated in update_do_one().
+ * @param array $operations
+ * A list of all the operations that had not been completed by the batch API.
+ */
+ public static function batchFinished($success, $results, $operations) {
+ // No updates to run, so caches won't get flushed later. Clear them now.
+ drupal_flush_all_caches();
+
+ $_SESSION['update_results'] = $results;
+ $_SESSION['update_success'] = $success;
+ $_SESSION['updates_remaining'] = $operations;
+
+ // Now that the update is done, we can put the site back online if it was
+ // previously in maintenance mode.
+ if (isset($_SESSION['maintenance_mode'])) {
+ \Drupal::state()->set('system.maintenance_mode', FALSE);
+ unset($_SESSION['maintenance_mode']);
+ }
+ }
+
+ /**
+ * Provides links to the homepage and administration pages.
+ *
+ * @return array
+ * An array of links.
+ */
+ protected function helpfulLinks() {
+ $links['front'] = array(
+ 'title' => $this->t('Front page'),
+ 'href' => '',
+ );
+ if ($this->account->hasPermission('access administration pages')) {
+ $links['admin-pages'] = array(
+ 'title' => $this->t('Administration pages'),
+ 'href' => 'admin',
+ );
+ }
+ return $links;
+ }
+
+ /**
+ * Retrieves module updates.
+ *
+ * @return array
+ * The module updates that can be performed.
+ */
+ protected function getModuleUpdates() {
+ $return = array();
+ $updates = update_get_update_list();
+ foreach ($updates as $module => $update) {
+ $return[$module] = $update['start'];
+ }
+
+ return $return;
+ }
+
+}
diff --git a/core/modules/system/src/Tests/Update/InvalidUpdateHookTest.php b/core/modules/system/src/Tests/Update/InvalidUpdateHookTest.php
index d07300adead0..b5ea8fc14bd5 100644
--- a/core/modules/system/src/Tests/Update/InvalidUpdateHookTest.php
+++ b/core/modules/system/src/Tests/Update/InvalidUpdateHookTest.php
@@ -43,7 +43,7 @@ class InvalidUpdateHookTest extends WebTestBase {
parent::setUp();
require_once DRUPAL_ROOT . '/core/includes/update.inc';
- $this->update_url = $GLOBALS['base_url'] . '/core/update.php';
+ $this->update_url = $GLOBALS['base_url'] . '/update.php';
$this->update_user = $this->drupalCreateUser(array('administer software updates'));
}
@@ -51,7 +51,7 @@ class InvalidUpdateHookTest extends WebTestBase {
// Confirm that a module with hook_update_8000() cannot be updated.
$this->drupalLogin($this->update_user);
$this->drupalGet($this->update_url);
- $this->drupalPostForm($this->update_url, array(), t('Continue'), array('external' => TRUE));
+ $this->clickLink(t('Continue'));
$this->assertText(t('Some of the pending updates cannot be applied because their dependencies were not met.'));
}
diff --git a/core/modules/system/src/Tests/Update/UpdateScriptTest.php b/core/modules/system/src/Tests/Update/UpdateScriptTest.php
index 54e72dca08d2..893682ec0e0b 100644
--- a/core/modules/system/src/Tests/Update/UpdateScriptTest.php
+++ b/core/modules/system/src/Tests/Update/UpdateScriptTest.php
@@ -30,30 +30,8 @@ class UpdateScriptTest extends WebTestBase {
protected function setUp() {
parent::setUp();
- $this->update_url = $GLOBALS['base_url'] . '/core/update.php';
- $this->update_user = $this->drupalCreateUser(array('administer software updates'));
- }
-
- /**
- * Tests that updates from schema versions prior to 8000 are prevented.
- */
- function testInvalidMigration() {
- // Mock a D7 system table so that the schema value of the system module
- // can be retrieved.
- db_create_table('system', $this->getSystemSchema());
- // Assert that the table exists.
- $this->assertTrue(db_table_exists('system'), 'The table exists.');
- // Insert a value for the system module.
- db_insert('system')
- ->fields(array(
- 'name' => 'system',
- 'schema_version' => 7000,
- ))
- ->execute();
- $system_schema = db_query('SELECT schema_version FROM {system} WHERE name = :system', array(':system' => 'system'))->fetchField();
- $this->drupalGet($this->update_url, array('external' => TRUE));
- $text = 'Your system schema version is ' . $system_schema . '. Updating directly from a schema version prior to 8000 is not supported. You must migrate your site to Drupal 8 first.';
- $this->assertRaw($text, 'Updates from schema versions prior to 8000 are prevented.');
+ $this->update_url = $GLOBALS['base_url'] . '/update.php';
+ $this->update_user = $this->drupalCreateUser(array('administer software updates', 'access site in maintenance mode'));
}
/**
@@ -92,7 +70,7 @@ class UpdateScriptTest extends WebTestBase {
// If there are no requirements warnings or errors, we expect to be able to
// go through the update process uninterrupted.
$this->drupalGet($this->update_url, array('external' => TRUE));
- $this->drupalPostForm(NULL, array(), t('Continue'));
+ $this->clickLink(t('Continue'));
$this->assertText(t('No pending updates.'), 'End of update process was reached.');
// Confirm that all caches were cleared.
$this->assertText(t('hook_cache_flush() invoked for update_script_test.module.'), 'Caches were cleared when there were no requirements warnings or errors.');
@@ -109,8 +87,8 @@ class UpdateScriptTest extends WebTestBase {
$this->assertText('This is a requirements warning provided by the update_script_test module.');
$this->clickLink('try again');
$this->assertNoText('This is a requirements warning provided by the update_script_test module.');
- $this->drupalPostForm(NULL, array(), t('Continue'));
- $this->drupalPostForm(NULL, array(), 'Apply pending updates');
+ $this->clickLink(t('Continue'));
+ $this->clickLink(t('Apply pending updates'));
$this->assertText(t('The update_script_test_update_8001() update was executed successfully.'), 'End of update process was reached.');
// Confirm that all caches were cleared.
$this->assertText(t('hook_cache_flush() invoked for update_script_test.module.'), 'Caches were cleared after resolving a requirements warning and applying updates.');
@@ -120,7 +98,7 @@ class UpdateScriptTest extends WebTestBase {
$this->assertText('This is a requirements warning provided by the update_script_test module.');
$this->clickLink('try again');
$this->assertNoText('This is a requirements warning provided by the update_script_test module.');
- $this->drupalPostForm(NULL, array(), t('Continue'));
+ $this->clickLink(t('Continue'));
$this->assertText(t('No pending updates.'), 'End of update process was reached.');
// Confirm that all caches were cleared.
$this->assertText(t('hook_cache_flush() invoked for update_script_test.module.'), 'Caches were cleared after applying updates and re-running the script.');
@@ -155,7 +133,8 @@ class UpdateScriptTest extends WebTestBase {
function testNoUpdateFunctionality() {
// Click through update.php with 'administer software updates' permission.
$this->drupalLogin($this->update_user);
- $this->drupalPostForm($this->update_url, array(), t('Continue'), array('external' => TRUE));
+ $this->drupalGet($this->update_url, array('external' => TRUE));
+ $this->clickLink(t('Continue'));
$this->assertText(t('No pending updates.'));
$this->assertNoLink('Administration pages');
$this->assertNoLinkByHref('update.php', 0);
@@ -165,7 +144,8 @@ class UpdateScriptTest extends WebTestBase {
// Click through update.php with 'access administration pages' permission.
$admin_user = $this->drupalCreateUser(array('administer software updates', 'access administration pages'));
$this->drupalLogin($admin_user);
- $this->drupalPostForm($this->update_url, array(), t('Continue'), array('external' => TRUE));
+ $this->drupalGet($this->update_url, array('external' => TRUE));
+ $this->clickLink(t('Continue'));
$this->assertText(t('No pending updates.'));
$this->assertLink('Administration pages');
$this->assertNoLinkByHref('update.php', 1);
@@ -187,8 +167,9 @@ class UpdateScriptTest extends WebTestBase {
// Click through update.php with 'administer software updates' permission.
$this->drupalLogin($this->update_user);
- $this->drupalPostForm($this->update_url, array(), t('Continue'), array('external' => TRUE));
- $this->drupalPostForm(NULL, array(), t('Apply pending updates'));
+ $this->drupalGet($this->update_url, array('external' => TRUE));
+ $this->clickLink(t('Continue'));
+ $this->clickLink(t('Apply pending updates'));
// Verify that updates were completed successfully.
$this->assertText('Updates were attempted.');
@@ -219,10 +200,11 @@ class UpdateScriptTest extends WebTestBase {
// Click through update.php with 'access administration pages' and
// 'access site reports' permissions.
- $admin_user = $this->drupalCreateUser(array('administer software updates', 'access administration pages', 'access site reports'));
+ $admin_user = $this->drupalCreateUser(array('administer software updates', 'access administration pages', 'access site reports', 'access site in maintenance mode'));
$this->drupalLogin($admin_user);
- $this->drupalPostForm($this->update_url, array(), t('Continue'), array('external' => TRUE));
- $this->drupalPostForm(NULL, array(), t('Apply pending updates'));
+ $this->drupalGet($this->update_url, array('external' => TRUE));
+ $this->clickLink(t('Continue'));
+ $this->clickLink(t('Apply pending updates'));
$this->assertText('Updates were attempted.');
$this->assertLink('logged');
$this->assertLink('Administration pages');
diff --git a/core/modules/system/src/Tests/Update/UpdatesWith7xTest.php b/core/modules/system/src/Tests/Update/UpdatesWith7xTest.php
index 7b04dc6892ab..b6c215daedc6 100644
--- a/core/modules/system/src/Tests/Update/UpdatesWith7xTest.php
+++ b/core/modules/system/src/Tests/Update/UpdatesWith7xTest.php
@@ -37,7 +37,7 @@ class UpdatesWith7xTest extends WebTestBase {
protected function setUp() {
parent::setUp();
require_once DRUPAL_ROOT . '/core/includes/update.inc';
- $this->update_url = $GLOBALS['base_url'] . '/core/update.php';
+ $this->update_url = $GLOBALS['base_url'] . '/update.php';
$this->update_user = $this->drupalCreateUser(array('administer software updates'));
}
@@ -52,7 +52,8 @@ class UpdatesWith7xTest extends WebTestBase {
// Click through update.php with 'administer software updates' permission.
$this->drupalLogin($this->update_user);
- $this->drupalPostForm($this->update_url, array(), t('Continue'), array('external' => TRUE));
+ $this->drupalGet($this->update_url, array('external' => TRUE));
+ $this->clickLink(t('Continue'));
$this->assertText(t('Some of the pending updates cannot be applied because their dependencies were not met.'));
}
}
diff --git a/core/modules/system/src/Theme/DbUpdateNegotiator.php b/core/modules/system/src/Theme/DbUpdateNegotiator.php
new file mode 100644
index 000000000000..260923ca453f
--- /dev/null
+++ b/core/modules/system/src/Theme/DbUpdateNegotiator.php
@@ -0,0 +1,57 @@
+configFactory = $config_factory;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function applies(RouteMatchInterface $route_match) {
+ return $route_match->getRouteName() == 'system.db_update';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function determineActiveTheme(RouteMatchInterface $route_match) {
+ $custom_theme = Settings::get('maintenance_theme', 'seven');
+ if (!$custom_theme) {
+ $config = $this->configFactory->get('system.theme');
+ $custom_theme = $config->get('default');
+ }
+
+ return $custom_theme;
+ }
+
+}
diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml
index ba2a46d26b35..80348633ed2b 100644
--- a/core/modules/system/system.routing.yml
+++ b/core/modules/system/system.routing.yml
@@ -407,8 +407,14 @@ system.batch_page.json:
options:
_admin_route: TRUE
-system.update:
- path: '/core/update.php'
+system.db_update:
+ path: '/update.php/{op}'
+ defaults:
+ _title: 'Drupal database update'
+ _controller: '\Drupal\system\Controller\DbUpdateController::handle'
+ op: 'info'
+ requirements:
+ _access_system_update: 'TRUE'
system.admin_content:
path: '/admin/content'
diff --git a/core/modules/system/system.services.yml b/core/modules/system/system.services.yml
index a5b94ced97d4..453eea4f9393 100644
--- a/core/modules/system/system.services.yml
+++ b/core/modules/system/system.services.yml
@@ -3,6 +3,10 @@ services:
class: Drupal\system\Access\CronAccessCheck
tags:
- { name: access_check, applies_to: _access_system_cron }
+ access_check.db_update:
+ class: Drupal\system\Access\DbUpdateAccessCheck
+ tags:
+ - { name: access_check, applies_to: _access_system_update }
system.manager:
class: Drupal\system\SystemManager
arguments: ['@module_handler', '@database', '@entity.manager', '@request_stack', '@menu.link_tree', '@menu.active_trail']
@@ -26,6 +30,11 @@ services:
arguments: ['@batch.storage', '@request_stack']
tags:
- { name: theme_negotiator, priority: 1000 }
+ theme.negotiator.system.db_update:
+ class: Drupal\system\Theme\DbUpdateNegotiator
+ arguments: ['@config.factory']
+ tags:
+ - { name: theme_negotiator, priority: 100 }
system.config_subscriber:
class: Drupal\system\SystemConfigSubscriber
tags:
diff --git a/core/update.php b/core/update.php
deleted file mode 100644
index 4e8157c46b8e..000000000000
--- a/core/update.php
+++ /dev/null
@@ -1,427 +0,0 @@
-system requirements page for more information.';
- exit;
-}
-
-/**
- * Global flag indicating that update.php is being run.
- *
- * When this flag is set, various operations do not take place, such as css/js
- * preprocessing and translation.
- *
- * This constant is defined using define() instead of const so that PHP
- * versions older than 5.3 can display the proper PHP requirements instead of
- * causing a fatal error.
- */
-define('MAINTENANCE_MODE', 'update');
-
-/**
- * Renders a form with a list of available database updates.
- */
-function update_selection_page() {
- // Make sure there is no stale theme registry.
- \Drupal::cache()->deleteAll();
-
- $build = \Drupal::formBuilder()->getForm('Drupal\Core\Update\Form\UpdateScriptSelectionForm');
- $build['#title'] = 'Drupal database update';
-
- return $build;
-}
-
-/**
- * Provides links to the homepage and administration pages.
- */
-function update_helpful_links() {
- $links['front'] = array(
- 'title' => t('Front page'),
- 'href' => '',
- );
- if (\Drupal::currentUser()->hasPermission('access administration pages')) {
- $links['admin-pages'] = array(
- 'title' => t('Administration pages'),
- 'href' => 'admin',
- );
- }
- return $links;
-}
-
-/**
- * Remove update overrides and flush all caches.
- *
- * This will need to be run once all (if any) updates are run. Do not call this
- * while updates are running.
- */
-function update_flush_all_caches() {
- $GLOBALS['conf']['update_service_provider_overrides'] = FALSE;
- \Drupal::service('kernel')->updateModules(\Drupal::moduleHandler()->getModuleList());
-
- // No updates to run, so caches won't get flushed later. Clear them now.
- drupal_flush_all_caches();
-}
-
-/**
- * Displays results of the update script with any accompanying errors.
- */
-function update_results_page() {
- // Report end result.
- if (\Drupal::moduleHandler()->moduleExists('dblog') && \Drupal::currentUser()->hasPermission('access site reports')) {
- $log_message = ' All errors have been logged.';
- }
- else {
- $log_message = ' All errors have been logged.';
- }
-
- if ($_SESSION['update_success']) {
- $output = '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.' . $log_message . '
';
- }
- else {
- $last = reset($_SESSION['updates_remaining']);
- list($module, $version) = array_pop($last);
- $output = 'The update process was aborted prematurely while running update #' . $version . ' in ' . $module . '.module.' . $log_message;
- if (\Drupal::moduleHandler()->moduleExists('dblog')) {
- $output .= ' You may need to check the watchdog database table manually.';
- }
- $output .= '
';
- }
-
- if (Settings::get('update_free_access')) {
- $output .= "Reminder: don't forget to set the \$settings['update_free_access'] value in your settings.php file back to FALSE.
";
- }
-
- $links = array(
- '#theme' => 'links',
- '#links' => update_helpful_links(),
- );
- $output .= drupal_render($links);
-
- // Output a list of queries executed.
- if (!empty($_SESSION['update_results'])) {
- $all_messages = '';
- foreach ($_SESSION['update_results'] as $module => $updates) {
- if ($module != '#abort') {
- $module_has_message = FALSE;
- $query_messages = '';
- foreach ($updates as $number => $queries) {
- $messages = array();
- foreach ($queries as $query) {
- // If there is no message for this update, don't show anything.
- if (empty($query['query'])) {
- continue;
- }
-
- if ($query['success']) {
- $messages[] = '' . $query['query'] . '';
- }
- else {
- $messages[] = 'Failed: ' . $query['query'] . '';
- }
- }
-
- if ($messages) {
- $module_has_message = TRUE;
- $query_messages .= 'Update #' . $number . "
\n";
- $query_messages .= '' . implode("\n", $messages) . "
\n";
- }
- }
-
- // If there were any messages in the queries then prefix them with the
- // module name and add it to the global message list.
- if ($module_has_message) {
- $all_messages .= '' . $module . " module
\n" . $query_messages;
- }
- }
- }
- if ($all_messages) {
- $output .= 'The following updates returned messages
';
- $output .= $all_messages;
- $output .= '';
- }
- }
- unset($_SESSION['update_results']);
- unset($_SESSION['update_success']);
-
- $build = array(
- '#title' => 'Drupal database update',
- '#markup' => $output,
- );
- return $build;
-}
-
-/**
- * Provides an overview of the Drupal database update.
- *
- * This page provides cautionary suggestions that should happen before
- * proceeding with the update to ensure data integrity.
- *
- * @return
- * Rendered HTML form.
- */
-function update_info_page() {
- // 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.
- $keyvalue = \Drupal::service('keyvalue.expirable');
- $keyvalue->get('update')->deleteAll();
- $keyvalue->get('update_available_release')->deleteAll();
-
- $token = \Drupal::csrfToken()->get('update');
- $output = 'Use this utility to update your database whenever a new release of Drupal or a module is installed.
For more detailed information, see the upgrading handbook. If you are unsure what these terms mean you should probably contact your hosting provider.
';
- $output .= "\n";
- $output .= "- 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.
\n";
- $output .= '- Put your site into maintenance mode.
' . "\n";
- $output .= "- Back up your database. This process will change your database values and in case of emergency you may need to revert to a backup.
\n";
- $output .= "- Install your new files in the appropriate location, as described in the handbook.
\n";
- $output .= "
\n";
- $output .= "When you have performed the steps above, you may proceed.
\n";
- $form_action = check_url(drupal_current_script_url(array('op' => 'selection', 'token' => $token)));
- $output .= '';
- $output .= "\n";
-
- $build = array(
- '#title' => 'Drupal database update',
- '#markup' => $output,
- );
- return $build;
-}
-
-/**
- * Renders a 403 access denied page for update.php.
- *
- * @return
- * Rendered HTML warning with 403 status.
- */
-function update_access_denied_page() {
- drupal_add_http_header('Status', '403 Forbidden');
- header(\Drupal::request()->server->get('SERVER_PROTOCOL') . ' 403 Forbidden');
- \Drupal::logger('access denied')->warning('update.php');
- $output = 'Access denied. You are not authorized to access this page. Log in using either an account with the administer software updates permission or the site maintenance account (the account you created during installation). If you cannot log in, you will have to edit settings.php to bypass this access check. To do this:
-
- - With a text editor find the settings.php file on your system. From the main Drupal directory that you installed all the files into, go to
sites/your_site_name if such directory exists, or else to sites/default which applies otherwise.
- - There is a line inside your settings.php file that says
$settings[\'update_free_access\'] = FALSE;. Change it to $settings[\'update_free_access\'] = TRUE;.
- - As soon as the update.php script is done, you must change the settings.php file back to its original form with
$settings[\'update_free_access\'] = FALSE;.
- - To avoid having this problem in the future, remember to log in to your website using either an account with the administer software updates permission or the site maintenance account (the account you created during installation) before you backup your database at the beginning of the update process.
-
';
-
- $build = array(
- '#title' => 'Access denied',
- '#markup' => $output,
- );
- return $build;
-}
-
-/**
- * Determines if the current user is allowed to run update.php.
- *
- * @return
- * TRUE if the current user should be granted access, or FALSE otherwise.
- */
-function update_access_allowed() {
- return Settings::get('update_free_access') || \Drupal::currentUser()->hasPermission('administer software updates');
-}
-
-/**
- * Adds the update task list to the current page.
- */
-function update_task_list($active = NULL) {
- // Default list of tasks.
- $tasks = array(
- 'requirements' => 'Verify requirements',
- 'info' => 'Overview',
- 'select' => 'Review updates',
- 'run' => 'Run updates',
- 'finished' => 'Review log',
- );
-
- $task_list = array(
- '#theme' => 'task_list',
- '#items' => $tasks,
- '#active' => $active,
- );
- return $task_list;
-}
-
-// Some unavoidable errors happen because the database is not yet up-to-date.
-// Our custom error handler is not yet installed, so we just suppress them.
-ini_set('display_errors', FALSE);
-
-// We prepare a minimal bootstrap for the update requirements check to avoid
-// reaching the PHP memory limit.
-require_once __DIR__ . '/includes/update.inc';
-require_once __DIR__ . '/includes/install.inc';
-
-$request = Request::createFromGlobals();
-$kernel = DrupalKernel::createFromRequest($request, $autoloader, 'update', FALSE);
-
-// Enable UpdateServiceProvider service overrides.
-// @see update_flush_all_caches()
-$GLOBALS['conf']['container_service_providers']['UpdateServiceProvider'] = 'Drupal\Core\DependencyInjection\UpdateServiceProvider';
-$GLOBALS['conf']['update_service_provider_overrides'] = TRUE;
-$kernel->boot();
-
-// Updating from a site schema version prior to 8000 should block the update
-// process. Ensure that the site is not attempting to update a database
-// created in a previous version of Drupal.
-if (db_table_exists('system')) {
- $system_schema = db_query('SELECT schema_version FROM {system} WHERE name = :system', array(':system' => 'system'))->fetchField();
- if ($system_schema < \Drupal::CORE_MINIMUM_SCHEMA_VERSION) {
- print 'Your system schema version is ' . $system_schema . '. Updating directly from a schema version prior to 8000 is not supported. You must migrate your site to Drupal 8 first.';
- exit;
- }
-}
-
-$kernel->prepareLegacyRequest($request);
-
-// Determine if the current user has access to run update.php.
-\Drupal::service('session_manager')->startLazy();
-
-// Ensure that URLs generated for the home and admin pages don't have 'update.php'
-// in them.
-$generator = \Drupal::urlGenerator();
-$generator->setBasePath(str_replace('/core', '', $request->getBasePath()) . '/');
-$generator->setScriptPath('');
-
-// There can be conflicting 'op' parameters because both update and batch use
-// this parameter name. We need the 'op' coming from a POST request to trump
-// that coming from a GET request.
-$op = $request->request->get('op');
-if (is_null($op)) {
- $op = $request->query->get('op');
-}
-
-// Only allow the requirements check to proceed if the current user has access
-// to run updates (since it may expose sensitive information about the site's
-// configuration).
-if (is_null($op) && update_access_allowed()) {
- require_once __DIR__ . '/includes/install.inc';
- require_once DRUPAL_ROOT . '/core/modules/system/system.install';
-
- // Set up theme system for the maintenance page.
- drupal_maintenance_theme();
-
- // Check the update requirements for Drupal. Only report on errors at this
- // stage, since the real requirements check happens further down.
- // The request will exit() if any requirement violations are reported in the
- // following function invocation.
- update_check_requirements(TRUE);
-
- // Redirect to the update information page if all requirements were met.
- install_goto('core/update.php?op=info');
-}
-
-drupal_maintenance_theme();
-
-// Turn error reporting back on. From now on, only fatal errors (which are
-// not passed through the error handler) will cause a message to be printed.
-ini_set('display_errors', TRUE);
-
-$regions = array();
-
-// Only proceed with updates if the user is allowed to run them.
-if (update_access_allowed()) {
-
- include_once __DIR__ . '/includes/install.inc';
- include_once __DIR__ . '/includes/batch.inc';
- drupal_load_updates();
-
- update_fix_compatibility();
-
- // Check the update requirements for all modules. If there are warnings, but
- // no errors, skip reporting them if the user has provided a URL parameter
- // acknowledging the warnings and indicating a desire to continue anyway. See
- // drupal_requirements_url().
- $continue = $request->query->get('continue');
- $skip_warnings = !empty($continue);
- update_check_requirements($skip_warnings);
-
- switch ($op) {
- // update.php ops.
-
- case 'selection':
- $token = $request->query->get('token');
- if (isset($token) && \Drupal::csrfToken()->validate($token, 'update')) {
- $regions['sidebar_first'] = update_task_list('select');
- $output = update_selection_page();
- break;
- }
-
- case 'Apply pending updates':
- $token = $request->query->get('token');
- if (isset($token) && \Drupal::csrfToken()->validate($token, 'update')) {
- $regions['sidebar_first'] = update_task_list('run');
- // Generate absolute URLs for the batch processing (using $base_root),
- // since the batch API will pass them to url() which does not handle
- // update.php correctly by default.
- $batch_url = $base_root . drupal_current_script_url();
- $redirect_url = $base_root . drupal_current_script_url(array('op' => 'results'));
- $output = update_batch($request->request->get('start'), $redirect_url, $batch_url);
- break;
- }
-
- case 'info':
- $regions['sidebar_first'] = update_task_list('info');
- $output = update_info_page();
- break;
-
- case 'results':
- $regions['sidebar_first'] = update_task_list();
- $output = update_results_page();
- break;
-
- // Regular batch ops : defer to batch processing API.
- default:
- $regions['sidebar_first'] = update_task_list('run');
- $output = _batch_page($request);
- break;
- }
-}
-else {
- $output = update_access_denied_page();
-}
-if (isset($output) && $output) {
- // Explicitly start a session so that the update.php token will be accepted.
- \Drupal::service('session_manager')->start();
- // We defer the display of messages until all updates are done.
- $progress_page = ($batch = batch_get()) && isset($batch['running']);
- if ($output instanceof Response) {
- $output->send();
- }
- else {
- drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
- print DefaultHtmlPageRenderer::renderPage($output, $output['#title'], 'maintenance', $regions + array(
- '#show_messages' => !$progress_page,
- ));
- }
-}