diff --git a/core/includes/form.inc b/core/includes/form.inc
index efbac99..1b116a3 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -243,7 +243,7 @@ function template_preprocess_fieldset(&$variables) {
  */
 function template_preprocess_details(&$variables) {
   $element = $variables['element'];
-  $variables['attributes'] = $element['#attributes'];
+  $variables['attributes'] = isset($element['#attributes']) ? $element['#attributes'] : new Attribute();
   $variables['summary_attributes'] = new Attribute();
   if (!empty($element['#title'])) {
     $variables['summary_attributes']['role'] = 'button';
diff --git a/core/includes/form.inc.orig b/core/includes/form.inc.orig
new file mode 100644
index 0000000..efbac99
--- /dev/null
+++ b/core/includes/form.inc.orig
@@ -0,0 +1,962 @@
+<?php
+
+/**
+ * @file
+ * Functions for form and batch generation and processing.
+ */
+
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\Component\Utility\Xss;
+use Drupal\Core\Database\Database;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Form\FormElementHelper;
+use Drupal\Core\Form\OptGroup;
+use Drupal\Core\Render\Element;
+use Drupal\Core\Template\Attribute;
+use Drupal\Core\Url;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+
+/**
+ * Prepares variables for select element templates.
+ *
+ * Default template: select.html.twig.
+ *
+ * It is possible to group options together; to do this, change the format of
+ * $options to an associative array in which the keys are group labels, and the
+ * values are associative arrays in the normal $options format.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the properties of the element.
+ *     Properties used: #title, #value, #options, #description, #extra,
+ *     #multiple, #required, #name, #attributes, #size.
+ */
+function template_preprocess_select(&$variables) {
+  $element = $variables['element'];
+  Element::setAttributes($element, array('id', 'name', 'size'));
+  Element\RenderElement::setAttributes($element, array('form-select'));
+
+  $variables['attributes'] = $element['#attributes'];
+  $variables['options'] = form_select_options($element);
+}
+
+/**
+ * Converts an options form element into a structured array for output.
+ *
+ * This function calls itself recursively to obtain the values for each optgroup
+ * within the list of options and when the function encounters an object with
+ * an 'options' property inside $element['#options'].
+ *
+ * @param array $element
+ *   An associative array containing the following key-value pairs:
+ *   - #multiple: Optional Boolean indicating if the user may select more than
+ *     one item.
+ *   - #options: An associative array of options to render as HTML. Each array
+ *     value can be a string, an array, or an object with an 'option' property:
+ *     - A string or integer key whose value is a translated string is
+ *       interpreted as a single HTML option element. Do not use placeholders
+ *       that sanitize data: doing so will lead to double-escaping. Note that
+ *       the key will be visible in the HTML and could be modified by malicious
+ *       users, so don't put sensitive information in it.
+ *     - A translated string key whose value is an array indicates a group of
+ *       options. The translated string is used as the label attribute for the
+ *       optgroup. Do not use placeholders to sanitize data: doing so will lead
+ *       to double-escaping. The array should contain the options you wish to
+ *       group and should follow the syntax of $element['#options'].
+ *     - If the function encounters a string or integer key whose value is an
+ *       object with an 'option' property, the key is ignored, the contents of
+ *       the option property are interpreted as $element['#options'], and the
+ *       resulting HTML is added to the output.
+ *   - #value: Optional integer, string, or array representing which option(s)
+ *     to pre-select when the list is first displayed. The integer or string
+ *     must match the key of an option in the '#options' list. If '#multiple' is
+ *     TRUE, this can be an array of integers or strings.
+ * @param array|null $choices
+ *   (optional) Either an associative array of options in the same format as
+ *   $element['#options'] above, or NULL. This parameter is only used internally
+ *   and is not intended to be passed in to the initial function call.
+ *
+ * @return mixed[]
+ *   A structured, possibly nested, array of options and optgroups for use in a
+ *   select form element.
+ *   - label: A translated string whose value is the text of a single HTML
+ *     option element, or the label attribute for an optgroup.
+ *   - options: Optional, array of options for an optgroup.
+ *   - selected: A boolean that indicates whether the option is selected when
+ *     rendered.
+ *   - type: A string that defines the element type. The value can be 'option'
+ *     or 'optgroup'.
+ *   - value: A string that contains the value attribute for the option.
+ */
+function form_select_options($element, $choices = NULL) {
+  if (!isset($choices)) {
+    if (empty($element['#options'])) {
+      return [];
+    }
+    $choices = $element['#options'];
+  }
+  // array_key_exists() accommodates the rare event where $element['#value'] is NULL.
+  // isset() fails in this situation.
+  $value_valid = isset($element['#value']) || array_key_exists('#value', $element);
+  $value_is_array = $value_valid && is_array($element['#value']);
+  // Check if the element is multiple select and no value has been selected.
+  $empty_value = (empty($element['#value']) && !empty($element['#multiple']));
+  $options = [];
+  foreach ($choices as $key => $choice) {
+    if (is_array($choice)) {
+      $options[] = [
+        'type' => 'optgroup',
+        'label' => $key,
+        'options' => form_select_options($element, $choice),
+      ];
+    }
+    elseif (is_object($choice) && isset($choice->option)) {
+      $options = array_merge($options, form_select_options($element, $choice->option));
+    }
+    else {
+      $option = [];
+      $key = (string) $key;
+      $empty_choice = $empty_value && $key == '_none';
+      if ($value_valid && ((!$value_is_array && (string) $element['#value'] === $key || ($value_is_array && in_array($key, $element['#value']))) || $empty_choice)) {
+        $option['selected'] = TRUE;
+      }
+      else {
+        $option['selected'] = FALSE;
+      }
+      $option['type'] = 'option';
+      $option['value'] = $key;
+      $option['label'] = $choice;
+      $options[] = $option;
+    }
+  }
+  return $options;
+}
+
+/**
+ * Returns the indexes of a select element's options matching a given key.
+ *
+ * This function is useful if you need to modify the options that are
+ * already in a form element; for example, to remove choices which are
+ * not valid because of additional filters imposed by another module.
+ * One example might be altering the choices in a taxonomy selector.
+ * To correctly handle the case of a multiple hierarchy taxonomy,
+ * #options arrays can now hold an array of objects, instead of a
+ * direct mapping of keys to labels, so that multiple choices in the
+ * selector can have the same key (and label). This makes it difficult
+ * to manipulate directly, which is why this helper function exists.
+ *
+ * This function does not support optgroups (when the elements of the
+ * #options array are themselves arrays), and will return FALSE if
+ * arrays are found. The caller must either flatten/restore or
+ * manually do their manipulations in this case, since returning the
+ * index is not sufficient, and supporting this would make the
+ * "helper" too complicated and cumbersome to be of any help.
+ *
+ * As usual with functions that can return array() or FALSE, do not
+ * forget to use === and !== if needed.
+ *
+ * @param $element
+ *   The select element to search.
+ * @param $key
+ *   The key to look for.
+ *
+ * @return
+ *   An array of indexes that match the given $key. Array will be
+ *   empty if no elements were found. FALSE if optgroups were found.
+ */
+function form_get_options($element, $key) {
+  $keys = array();
+  foreach ($element['#options'] as $index => $choice) {
+    if (is_array($choice)) {
+      return FALSE;
+    }
+    elseif (is_object($choice)) {
+      if (isset($choice->option[$key])) {
+        $keys[] = $index;
+      }
+    }
+    elseif ($index == $key) {
+      $keys[] = $index;
+    }
+  }
+  return $keys;
+}
+
+/**
+ * Prepares variables for fieldset element templates.
+ *
+ * Default template: fieldset.html.twig.
+ *
+ * @param array $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the properties of the element.
+ *     Properties used: #attributes, #children, #description, #id, #title,
+ *     #value.
+ */
+function template_preprocess_fieldset(&$variables) {
+  $element = $variables['element'];
+  Element::setAttributes($element, array('id'));
+  Element\RenderElement::setAttributes($element);
+  $variables['attributes'] = $element['#attributes'];
+  $variables['prefix'] = isset($element['#field_prefix']) ? $element['#field_prefix'] : NULL;
+  $variables['suffix'] = isset($element['#field_suffix']) ? $element['#field_suffix'] : NULL;
+  $variables['title_display'] = isset($element['#title_display']) ? $element['#title_display'] : NULL;
+  $variables['children'] = $element['#children'];
+  $variables['required'] = !empty($element['#required']) ? $element['#required'] : NULL;
+
+  if (isset($element['#title']) && $element['#title'] !== '') {
+    $variables['legend']['title'] = ['#markup' => $element['#title']];
+  }
+
+  $variables['legend']['attributes'] = new Attribute();
+  $variables['legend_span']['attributes'] = new Attribute();
+
+  if (!empty($element['#description'])) {
+    $description_id = $element['#attributes']['id'] . '--description';
+    $description_attributes['id'] = $description_id;
+    $variables['description']['attributes'] = new Attribute($description_attributes);
+    $variables['description']['content'] = $element['#description'];
+
+    // Add the description's id to the fieldset aria attributes.
+    $variables['attributes']['aria-describedby'] = $description_id;
+  }
+
+  // Display any error messages.
+  $variables['errors'] = NULL;
+  if (!empty($element['#errors']) && empty($element['#error_no_message'])) {
+    $variables['errors'] = $element['#errors'];
+  }
+}
+
+/**
+ * Prepares variables for details element templates.
+ *
+ * Default template: details.html.twig.
+ *
+ * @param array $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the properties of the element.
+ *     Properties used: #attributes, #children, #open,
+ *     #description, #id, #title, #value, #optional.
+ */
+function template_preprocess_details(&$variables) {
+  $element = $variables['element'];
+  $variables['attributes'] = $element['#attributes'];
+  $variables['summary_attributes'] = new Attribute();
+  if (!empty($element['#title'])) {
+    $variables['summary_attributes']['role'] = 'button';
+    if (!empty($element['#attributes']['id'])) {
+      $variables['summary_attributes']['aria-controls'] = $element['#attributes']['id'];
+    }
+    $variables['summary_attributes']['aria-expanded'] = !empty($element['#attributes']['open']) ? 'true' : 'false';
+    $variables['summary_attributes']['aria-pressed'] = $variables['summary_attributes']['aria-expanded'];
+  }
+  $variables['title'] = (!empty($element['#title'])) ? $element['#title'] : '';
+  $variables['description'] = (!empty($element['#description'])) ? $element['#description'] : '';
+  $variables['children'] = (isset($element['#children'])) ? $element['#children'] : '';
+  $variables['value'] = (isset($element['#value'])) ? $element['#value'] : '';
+}
+
+/**
+ * Prepares variables for radios templates.
+ *
+ * Default template: radios.html.twig.
+ *
+ * @param array $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the properties of the element.
+ *     Properties used: #title, #value, #options, #description, #required,
+ *     #attributes, #children.
+ */
+function template_preprocess_radios(&$variables) {
+  $element = $variables['element'];
+  $variables['attributes'] = array();
+  if (isset($element['#id'])) {
+    $variables['attributes']['id'] = $element['#id'];
+  }
+  if (isset($element['#attributes']['title'])) {
+    $variables['attributes']['title'] = $element['#attributes']['title'];
+  }
+  $variables['children'] = $element['#children'];
+}
+
+/**
+ * Prepares variables for checkboxes templates.
+ *
+ * Default template: checkboxes.html.twig.
+ *
+ * @param array $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the properties of the element.
+ *     Properties used: #children, #attributes.
+ */
+function template_preprocess_checkboxes(&$variables) {
+  $element = $variables['element'];
+  $variables['attributes'] = array();
+  if (isset($element['#id'])) {
+    $variables['attributes']['id'] = $element['#id'];
+  }
+  if (isset($element['#attributes']['title'])) {
+    $variables['attributes']['title'] = $element['#attributes']['title'];
+  }
+  $variables['children'] = $element['#children'];
+}
+
+/**
+ * Prepares variables for vertical tabs templates.
+ *
+ * Default template: vertical-tabs.html.twig.
+ *
+ * @param array $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the properties and children of
+ *     the details element. Properties used: #children.
+ *
+ */
+function template_preprocess_vertical_tabs(&$variables) {
+  $element = $variables['element'];
+  $variables['children'] = (!empty($element['#children'])) ? $element['#children'] : '';
+}
+
+/**
+ * Prepares variables for input templates.
+ *
+ * Default template: input.html.twig.
+ *
+ * @param array $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the properties of the element.
+ *     Properties used: #attributes.
+ */
+function template_preprocess_input(&$variables) {
+  $element = $variables['element'];
+  $variables['children'] = $element['#children'];
+}
+
+/**
+ * Prepares variables for form templates.
+ *
+ * Default template: form.html.twig.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the properties of the element.
+ *     Properties used: #action, #method, #attributes, #children
+ */
+function template_preprocess_form(&$variables) {
+  $element = $variables['element'];
+  if (isset($element['#action'])) {
+    $element['#attributes']['action'] = UrlHelper::stripDangerousProtocols($element['#action']);
+  }
+  Element::setAttributes($element, array('method', 'id'));
+  if (empty($element['#attributes']['accept-charset'])) {
+    $element['#attributes']['accept-charset'] = "UTF-8";
+  }
+  $variables['attributes'] = $element['#attributes'];
+  $variables['children'] = $element['#children'];
+}
+
+/**
+ * Prepares variables for textarea templates.
+ *
+ * Default template: textarea.html.twig.
+ *
+ * @param array $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the properties of the element.
+ *     Properties used: #title, #value, #description, #rows, #cols,
+ *     #placeholder, #required, #attributes, #resizable
+ *
+ */
+function template_preprocess_textarea(&$variables) {
+  $element = $variables['element'];
+  Element::setAttributes($element, array('id', 'name', 'rows', 'cols', 'placeholder'));
+  Element\RenderElement::setAttributes($element, array('form-textarea'));
+  $variables['wrapper_attributes'] = new Attribute();
+  $variables['attributes'] = new Attribute($element['#attributes']);
+  $variables['value'] = $element['#value'];
+  $variables['resizable'] = !empty($element['#resizable']) ? $element['#resizable'] : NULL;
+  $variables['required'] = !empty($element['#required']) ? $element['#required'] : NULL;
+}
+
+/**
+ * Returns HTML for a form element.
+ * Prepares variables for form element templates.
+ *
+ * Default template: form-element.html.twig.
+ *
+ * In addition to the element itself, the DIV contains a label for the element
+ * based on the optional #title_display property, and an optional #description.
+ *
+ * The optional #title_display property can have these values:
+ * - before: The label is output before the element. This is the default.
+ *   The label includes the #title and the required marker, if #required.
+ * - after: The label is output after the element. For example, this is used
+ *   for radio and checkbox #type elements. If the #title is empty but the field
+ *   is #required, the label will contain only the required marker.
+ * - invisible: Labels are critical for screen readers to enable them to
+ *   properly navigate through forms but can be visually distracting. This
+ *   property hides the label for everyone except screen readers.
+ * - attribute: Set the title attribute on the element to create a tooltip
+ *   but output no label element. This is supported only for checkboxes
+ *   and radios in
+ *   \Drupal\Core\Render\Element\CompositeFormElementTrait::preRenderCompositeFormElement().
+ *   It is used where a visual label is not needed, such as a table of
+ *   checkboxes where the row and column provide the context. The tooltip will
+ *   include the title and required marker.
+ *
+ * If the #title property is not set, then the label and any required marker
+ * will not be output, regardless of the #title_display or #required values.
+ * This can be useful in cases such as the password_confirm element, which
+ * creates children elements that have their own labels and required markers,
+ * but the parent element should have neither. Use this carefully because a
+ * field without an associated label can cause accessibility challenges.
+ *
+ * @param array $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the properties of the element.
+ *     Properties used: #title, #title_display, #description, #id, #required,
+ *     #children, #type, #name.
+ */
+function template_preprocess_form_element(&$variables) {
+  $element = &$variables['element'];
+
+  // This function is invoked as theme wrapper, but the rendered form element
+  // may not necessarily have been processed by
+  // \Drupal::formBuilder()->doBuildForm().
+  $element += array(
+    '#title_display' => 'before',
+    '#wrapper_attributes' => array(),
+  );
+  $variables['attributes'] = $element['#wrapper_attributes'];
+
+  // Add element #id for #type 'item' and 'password_confirm'.
+  if (isset($element['#markup']) && !empty($element['#id'])) {
+    $variables['attributes']['id'] = $element['#id'];
+  }
+
+  // Pass elements #type and #name to template.
+  if (!empty($element['#type'])) {
+    $variables['type'] = $element['#type'];
+  }
+  if (!empty($element['#name'])) {
+    $variables['name'] = $element['#name'];
+  }
+
+  // Pass elements disabled status to template.
+  $variables['disabled'] = !empty($element['#attributes']['disabled']) ? $element['#attributes']['disabled'] : NULL;
+
+  // Display any error messages.
+  $variables['errors'] = NULL;
+  if (!empty($element['#errors']) && empty($element['#error_no_message'])) {
+    $variables['errors'] = $element['#errors'];
+  }
+
+  // If #title is not set, we don't display any label.
+  if (!isset($element['#title'])) {
+    $element['#title_display'] = 'none';
+  }
+
+  $variables['title_display'] = $element['#title_display'];
+
+  $variables['prefix'] = isset($element['#field_prefix']) ? $element['#field_prefix'] : NULL;
+  $variables['suffix'] = isset($element['#field_suffix']) ? $element['#field_suffix'] : NULL;
+
+  $variables['description'] = NULL;
+  if (!empty($element['#description'])) {
+    $variables['description_display'] = $element['#description_display'];
+    $description_attributes = [];
+    if (!empty($element['#id'])) {
+      $description_attributes['id'] = $element['#id'] . '--description';
+    }
+    $variables['description']['attributes'] = new Attribute($description_attributes);
+    $variables['description']['content'] = $element['#description'];
+  }
+
+  // Add label_display and label variables to template.
+  $variables['label_display'] = $element['#title_display'];
+  $variables['label'] = array('#theme' => 'form_element_label');
+  $variables['label'] += array_intersect_key($element, array_flip(array('#id', '#required', '#title', '#title_display')));
+
+  $variables['children'] = $element['#children'];
+}
+
+/**
+ * Prepares variables for form label templates.
+ *
+ * Form element labels include the #title and a #required marker. The label is
+ * associated with the element itself by the element #id. Labels may appear
+ * before or after elements, depending on form-element.html.twig and
+ * #title_display.
+ *
+ * This function will not be called for elements with no labels, depending on
+ * #title_display. For elements that have an empty #title and are not required,
+ * this function will output no label (''). For required elements that have an
+ * empty #title, this will output the required marker alone within the label.
+ * The label will use the #id to associate the marker with the field that is
+ * required. That is especially important for screenreader users to know
+ * which field is required.
+ *
+ * @param array $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the properties of the element.
+ *     Properties used: #required, #title, #id, #value, #description.
+ */
+function template_preprocess_form_element_label(&$variables) {
+  $element = $variables['element'];
+  // If title and required marker are both empty, output no label.
+  if (isset($element['#title']) && $element['#title'] !== '') {
+    $variables['title'] = ['#markup' => $element['#title']];
+  }
+
+  $variables['attributes'] = array();
+
+  // Pass elements title_display to template.
+  $variables['title_display'] = $element['#title_display'];
+
+  // A #for property of a dedicated #type 'label' element as precedence.
+  if (!empty($element['#for'])) {
+    $variables['attributes']['for'] = $element['#for'];
+    // A custom #id allows the referenced form input element to refer back to
+    // the label element; e.g., in the 'aria-labelledby' attribute.
+    if (!empty($element['#id'])) {
+      $variables['attributes']['id'] = $element['#id'];
+    }
+  }
+  // Otherwise, point to the #id of the form input element.
+  elseif (!empty($element['#id'])) {
+    $variables['attributes']['for'] = $element['#id'];
+  }
+
+  // Pass elements required to template.
+  $variables['required'] = !empty($element['#required']) ? $element['#required'] : NULL;
+}
+
+/**
+ * @defgroup batch Batch operations
+ * @{
+ * Creates and processes batch operations.
+ *
+ * Functions allowing forms processing to be spread out over several page
+ * requests, thus ensuring that the processing does not get interrupted
+ * because of a PHP timeout, while allowing the user to receive feedback
+ * on the progress of the ongoing operations.
+ *
+ * The API is primarily designed to integrate nicely with the Form API
+ * workflow, but can also be used by non-Form API scripts (like update.php)
+ * or even simple page callbacks (which should probably be used sparingly).
+ *
+ * Example:
+ * @code
+ * $batch = array(
+ *   'title' => t('Exporting'),
+ *   'operations' => array(
+ *     array('my_function_1', array($account->id(), 'story')),
+ *     array('my_function_2', array()),
+ *   ),
+ *   'finished' => 'my_finished_callback',
+ *   'file' => 'path_to_file_containing_myfunctions',
+ * );
+ * batch_set($batch);
+ * // Only needed if not inside a form _submit handler.
+ * // Setting redirect in batch_process.
+ * batch_process('node/1');
+ * @endcode
+ *
+ * Note: if the batch 'title', 'init_message', 'progress_message', or
+ * 'error_message' could contain any user input, it is the responsibility of
+ * the code calling batch_set() to sanitize them first with a function like
+ * \Drupal\Component\Utility\SafeMarkup::checkPlain() or
+ * \Drupal\Component\Utility\Xss::filter(). Furthermore, if the batch operation
+ * returns any user input in the 'results' or 'message' keys of $context, it
+ * must also sanitize them first.
+ *
+ * Sample callback_batch_operation():
+ * @code
+ * // Simple and artificial: load a node of a given type for a given user
+ * function my_function_1($uid, $type, &$context) {
+ *   // The $context array gathers batch context information about the execution (read),
+ *   // as well as 'return values' for the current operation (write)
+ *   // The following keys are provided :
+ *   // 'results' (read / write): The array of results gathered so far by
+ *   //   the batch processing, for the current operation to append its own.
+ *   // 'message' (write): A text message displayed in the progress page.
+ *   // The following keys allow for multi-step operations :
+ *   // 'sandbox' (read / write): An array that can be freely used to
+ *   //   store persistent data between iterations. It is recommended to
+ *   //   use this instead of $_SESSION, which is unsafe if the user
+ *   //   continues browsing in a separate window while the batch is processing.
+ *   // 'finished' (write): A float number between 0 and 1 informing
+ *   //   the processing engine of the completion level for the operation.
+ *   //   1 (or no value explicitly set) means the operation is finished
+ *   //   and the batch processing can continue to the next operation.
+ *
+ *   $nodes = entity_load_multiple_by_properties('node', array('uid' => $uid, 'type' => $type));
+ *   $node = reset($nodes);
+ *   $context['results'][] = $node->id() . ' : ' . SafeMarkup::checkPlain($node->label());
+ *   $context['message'] = SafeMarkup::checkPlain($node->label());
+ * }
+ *
+ * // A more advanced example is a multi-step operation that loads all rows,
+ * // five by five.
+ * function my_function_2(&$context) {
+ *   if (empty($context['sandbox'])) {
+ *     $context['sandbox']['progress'] = 0;
+ *     $context['sandbox']['current_id'] = 0;
+ *     $context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT id) FROM {example}')->fetchField();
+ *   }
+ *   $limit = 5;
+ *   $result = db_select('example')
+ *     ->fields('example', array('id'))
+ *     ->condition('id', $context['sandbox']['current_id'], '>')
+ *     ->orderBy('id')
+ *     ->range(0, $limit)
+ *     ->execute();
+ *   foreach ($result as $row) {
+ *     $context['results'][] = $row->id . ' : ' . SafeMarkup::checkPlain($row->title);
+ *     $context['sandbox']['progress']++;
+ *     $context['sandbox']['current_id'] = $row->id;
+ *     $context['message'] = SafeMarkup::checkPlain($row->title);
+ *   }
+ *   if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
+ *     $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+ *   }
+ * }
+ * @endcode
+ *
+ * Sample callback_batch_finished():
+ * @code
+ * function my_finished_callback($success, $results, $operations) {
+ *   // The 'success' parameter means no fatal PHP errors were detected. All
+ *   // other error management should be handled using 'results'.
+ *   if ($success) {
+ *     $message = \Drupal::translation()->formatPlural(count($results), 'One post processed.', '@count posts processed.');
+ *   }
+ *   else {
+ *     $message = t('Finished with an error.');
+ *   }
+ *   drupal_set_message($message);
+ *   // Providing data for the redirected page is done through $_SESSION.
+ *   foreach ($results as $result) {
+ *     $items[] = t('Loaded node %title.', array('%title' => $result));
+ *   }
+ *   $_SESSION['my_batch_results'] = $items;
+ * }
+ * @endcode
+ */
+
+/**
+ * Adds a new batch.
+ *
+ * Batch operations are added as new batch sets. Batch sets are used to spread
+ * processing (primarily, but not exclusively, forms processing) over several
+ * page requests. This helps to ensure that the processing is not interrupted
+ * due to PHP timeouts, while users are still able to receive feedback on the
+ * progress of the ongoing operations. Combining related operations into
+ * distinct batch sets provides clean code independence for each batch set,
+ * ensuring that two or more batches, submitted independently, can be processed
+ * without mutual interference. Each batch set may specify its own set of
+ * operations and results, produce its own UI messages, and trigger its own
+ * 'finished' callback. Batch sets are processed sequentially, with the progress
+ * bar starting afresh for each new set.
+ *
+ * @param $batch_definition
+ *   An associative array defining the batch, with the following elements (all
+ *   are optional except as noted):
+ *   - operations: (required) Array of operations to be performed, where each
+ *     item is an array consisting of the name of an implementation of
+ *     callback_batch_operation() and an array of parameter.
+ *     Example:
+ *     @code
+ *     array(
+ *       array('callback_batch_operation_1', array($arg1)),
+ *       array('callback_batch_operation_2', array($arg2_1, $arg2_2)),
+ *     )
+ *     @endcode
+ *   - title: A safe, translated string to use as the title for the progress
+ *     page. Defaults to t('Processing').
+ *   - init_message: Message displayed while the processing is initialized.
+ *     Defaults to t('Initializing.').
+ *   - progress_message: Message displayed while processing the batch. Available
+ *     placeholders are @current, @remaining, @total, @percentage, @estimate and
+ *     @elapsed. Defaults to t('Completed @current of @total.').
+ *   - error_message: Message displayed if an error occurred while processing
+ *     the batch. Defaults to t('An error has occurred.').
+ *   - finished: Name of an implementation of callback_batch_finished(). This is
+ *     executed after the batch has completed. This should be used to perform
+ *     any result massaging that may be needed, and possibly save data in
+ *     $_SESSION for display after final page redirection.
+ *   - file: Path to the file containing the definitions of the 'operations' and
+ *     'finished' functions, for instance if they don't reside in the main
+ *     .module file. The path should be relative to base_path(), and thus should
+ *     be built using drupal_get_path().
+ *   - css: Array of paths to CSS files to be used on the progress page.
+ *   - url_options: options passed to url() when constructing redirect URLs for
+ *     the batch.
+ *   - safe_strings: Internal use only. Used to store and retrieve strings
+ *     marked as safe between requests.
+ *   - progressive: A Boolean that indicates whether or not the batch needs to
+ *     run progressively. TRUE indicates that the batch will run in more than
+ *     one run. FALSE (default) indicates that the batch will finish in a single
+ *     run.
+ *   - queue: An override of the default queue (with name and class fields
+ *     optional). An array containing two elements:
+ *     - name: Unique identifier for the queue.
+ *     - class: The name of a class that implements
+ *       \Drupal\Core\Queue\QueueInterface, including the full namespace but not
+ *       starting with a backslash. It must have a constructor with two
+ *       arguments: $name and a \Drupal\Core\Database\Connection object.
+ *       Typically, the class will either be \Drupal\Core\Queue\Batch or
+ *       \Drupal\Core\Queue\BatchMemory. Defaults to Batch if progressive is
+ *       TRUE, or to BatchMemory if progressive is FALSE.
+ */
+function batch_set($batch_definition) {
+  if ($batch_definition) {
+    $batch =& batch_get();
+
+    // Initialize the batch if needed.
+    if (empty($batch)) {
+      $batch = array(
+        'sets' => array(),
+        'has_form_submits' => FALSE,
+      );
+    }
+
+    // Base and default properties for the batch set.
+    $init = array(
+      'sandbox' => array(),
+      'results' => array(),
+      'success' => FALSE,
+      'start' => 0,
+      'elapsed' => 0,
+    );
+    $defaults = array(
+      'title' => t('Processing'),
+      'init_message' => t('Initializing.'),
+      'progress_message' => t('Completed @current of @total.'),
+      'error_message' => t('An error has occurred.'),
+      'css' => array(),
+    );
+    $batch_set = $init + $batch_definition + $defaults;
+
+    // Tweak init_message to avoid the bottom of the page flickering down after
+    // init phase.
+    $batch_set['init_message'] .= '<br/>&nbsp;';
+
+    // The non-concurrent workflow of batch execution allows us to save
+    // numberOfItems() queries by handling our own counter.
+    $batch_set['total'] = count($batch_set['operations']);
+    $batch_set['count'] = $batch_set['total'];
+
+    // Add the set to the batch.
+    if (empty($batch['id'])) {
+      // The batch is not running yet. Simply add the new set.
+      $batch['sets'][] = $batch_set;
+    }
+    else {
+      // The set is being added while the batch is running. Insert the new set
+      // right after the current one to ensure execution order, and store its
+      // operations in a queue.
+      $index = $batch['current_set'] + 1;
+      $slice1 = array_slice($batch['sets'], 0, $index);
+      $slice2 = array_slice($batch['sets'], $index);
+      $batch['sets'] = array_merge($slice1, array($batch_set), $slice2);
+      _batch_populate_queue($batch, $index);
+    }
+  }
+}
+
+/**
+ * Processes the batch.
+ *
+ * This function is generally not needed in form submit handlers;
+ * Form API takes care of batches that were set during form submission.
+ *
+ * @param \Drupal\Core\Url|string $redirect
+ *   (optional) Either path or Url object to redirect to when the batch has
+ *   finished processing. Note that to simply force a batch to (conditionally)
+ *   redirect to a custom location after it is finished processing but to
+ *   otherwise allow the standard form API batch handling to occur, it is not
+ *   necessary to call batch_process() and use this parameter. Instead, make
+ *   the batch 'finished' callback return an instance of
+ *   \Symfony\Component\HttpFoundation\RedirectResponse, which will be used
+ *   automatically by the standard batch processing pipeline (and which takes
+ *   precedence over this parameter).
+ * @param \Drupal\Core\Url $url
+ *   (optional - should only be used for separate scripts like update.php)
+ *   URL of the batch processing page.
+ * @param $redirect_callback
+ *   (optional) Specify a function to be called to redirect to the progressive
+ *   processing page.
+ *
+ * @return \Symfony\Component\HttpFoundation\RedirectResponse|null
+ *   A redirect response if the batch is progressive. No return value otherwise.
+ */
+function batch_process($redirect = NULL, Url $url = NULL, $redirect_callback = NULL) {
+  $batch =& batch_get();
+
+  if (isset($batch)) {
+    // Add process information
+    $process_info = array(
+      'current_set' => 0,
+      'progressive' => TRUE,
+      'url' => isset($url) ? $url : Url::fromRoute('system.batch_page.html'),
+      'source_url' => Url::fromRouteMatch(\Drupal::routeMatch()),
+      'batch_redirect' => $redirect,
+      'theme' => \Drupal::theme()->getActiveTheme()->getName(),
+      'redirect_callback' => $redirect_callback,
+    );
+    $batch += $process_info;
+
+    // The batch is now completely built. Allow other modules to make changes
+    // to the batch so that it is easier to reuse batch processes in other
+    // environments.
+    \Drupal::moduleHandler()->alter('batch', $batch);
+
+    // Assign an arbitrary id: don't rely on a serial column in the 'batch'
+    // table, since non-progressive batches skip database storage completely.
+    $batch['id'] = db_next_id();
+
+    // Move operations to a job queue. Non-progressive batches will use a
+    // memory-based queue.
+    foreach ($batch['sets'] as $key => $batch_set) {
+      _batch_populate_queue($batch, $key);
+    }
+
+    // Initiate processing.
+    if ($batch['progressive']) {
+      // Now that we have a batch id, we can generate the redirection link in
+      // the generic error message.
+      /** @var \Drupal\Core\Url $batch_url */
+      $batch_url = $batch['url'];
+      /** @var \Drupal\Core\Url $error_url */
+      $error_url = clone $batch_url;
+      $query_options = $error_url->getOption('query');
+      $query_options['id'] = $batch['id'];
+      $query_options['op'] = 'finished';
+      $error_url->setOption('query', $query_options);
+
+      $batch['error_message'] = t('Please continue to <a href="@error_url">the error page</a>', array('@error_url' => $error_url->toString(TRUE)->getGeneratedUrl()));
+
+      // Clear the way for the redirection to the batch processing page, by
+      // saving and unsetting the 'destination', if there is any.
+      $request = \Drupal::request();
+      if ($request->query->has('destination')) {
+        $batch['destination'] = $request->query->get('destination');
+        $request->query->remove('destination');
+      }
+
+      // Store safe strings.
+      // @todo Ensure we are not storing an excessively large string list in:
+      //   https://www.drupal.org/node/2295823
+      $batch['safe_strings'] = SafeMarkup::getAll();
+
+      // Store the batch.
+      \Drupal::service('batch.storage')->create($batch);
+
+      // Set the batch number in the session to guarantee that it will stay alive.
+      $_SESSION['batches'][$batch['id']] = TRUE;
+
+      // Redirect for processing.
+      $query_options = $error_url->getOption('query');
+      $query_options['op'] = 'start';
+      $query_options['id'] = $batch['id'];
+      $batch_url->setOption('query', $query_options);
+      if (($function = $batch['redirect_callback']) && function_exists($function)) {
+        $function($batch_url->toString(), ['query' => $query_options]);
+      }
+      else {
+        return new RedirectResponse($batch_url->setAbsolute()->toString(TRUE)->getGeneratedUrl());
+      }
+    }
+    else {
+      // Non-progressive execution: bypass the whole progressbar workflow
+      // and execute the batch in one pass.
+      require_once __DIR__ . '/batch.inc';
+      _batch_process();
+    }
+  }
+}
+
+/**
+ * Retrieves the current batch.
+ */
+function &batch_get() {
+  // Not drupal_static(), because Batch API operates at a lower level than most
+  // use-cases for resetting static variables, and we specifically do not want a
+  // global drupal_static_reset() resetting the batch information. Functions
+  // that are part of the Batch API and need to reset the batch information may
+  // call batch_get() and manipulate the result by reference. Functions that are
+  // not part of the Batch API can also do this, but shouldn't.
+  static $batch = array();
+  return $batch;
+}
+
+/**
+ * Populates a job queue with the operations of a batch set.
+ *
+ * Depending on whether the batch is progressive or not, the
+ * Drupal\Core\Queue\Batch or Drupal\Core\Queue\BatchMemory handler classes will
+ * be used.
+ *
+ * @param $batch
+ *   The batch array.
+ * @param $set_id
+ *   The id of the set to process.
+ *
+ * @return
+ *   The name and class of the queue are added by reference to the batch set.
+ */
+function _batch_populate_queue(&$batch, $set_id) {
+  $batch_set = &$batch['sets'][$set_id];
+
+  if (isset($batch_set['operations'])) {
+    $batch_set += array(
+      'queue' => array(
+        'name' => 'drupal_batch:' . $batch['id'] . ':' . $set_id,
+        'class' => $batch['progressive'] ? 'Drupal\Core\Queue\Batch' : 'Drupal\Core\Queue\BatchMemory',
+      ),
+    );
+
+    $queue = _batch_queue($batch_set);
+    $queue->createQueue();
+    foreach ($batch_set['operations'] as $operation) {
+      $queue->createItem($operation);
+    }
+
+    unset($batch_set['operations']);
+  }
+}
+
+/**
+ * Returns a queue object for a batch set.
+ *
+ * @param $batch_set
+ *   The batch set.
+ *
+ * @return
+ *   The queue object.
+ */
+function _batch_queue($batch_set) {
+  static $queues;
+
+  if (!isset($queues)) {
+    $queues = array();
+  }
+
+  if (isset($batch_set['queue'])) {
+    $name = $batch_set['queue']['name'];
+    $class = $batch_set['queue']['class'];
+
+    if (!isset($queues[$class][$name])) {
+      $queues[$class][$name] = new $class($name, \Drupal::database());
+    }
+    return $queues[$class][$name];
+  }
+}
+
+/**
+ * @} End of "defgroup batch".
+ */
diff --git a/core/modules/system/css/system.admin.css b/core/modules/system/css/system.admin.css
index dead982..0a08623 100644
--- a/core/modules/system/css/system.admin.css
+++ b/core/modules/system/css/system.admin.css
@@ -76,84 +76,87 @@ small .admin-link:after {
 /**
  * Modules page.
  */
-.system-modules thead > tr {
+.system-projects thead > tr {
   border: 0;
 }
-.system-modules div.incompatible {
+.system-projects div.incompatible {
   font-weight: bold;
 }
-.system-modules td.checkbox {
+.system-projects td.checkbox {
   min-width: 25px;
   width: 4%;
 }
-.system-modules td.module {
+.system-projects td.module {
   width: 25%;
 }
-.system-modules td {
+.system-projects td {
   vertical-align: top;
 }
-.system-modules label,
-.system-modules-uninstall label {
+.system-projects label,
+.system-projects-uninstall label {
   color: #1d1d1d;
   font-size: 1.15em;
 }
-.system-modules details {
+.system-projects details {
   color: #5c5c5b;
   line-height: 20px;
-  overflow: hidden; /* truncates descriptions if too long */
-  text-overflow: ellipsis;
-  white-space: nowrap;
 }
-.system-modules details[open] {
+.system-projects .description details[open] {
   height: auto;
-  overflow: visible;
-  white-space: normal;
 }
-.system-modules details[open] summary .text {
+.system-projects .description details[open] > summary .text {
   -webkit-hyphens: auto;
   -moz-hyphens: auto;
   -ms-hyphens: auto;
   hyphens: auto;
   text-transform: none;
 }
-.system-modules td details a {
-  color: #5C5C5B;
+.system-projects .description details a {
+  color: #5c5c5B;
   border: 0px;
 }
-.system-modules td details {
+.system-projects .description details {
   border: 0;
   margin: 0;
   height: 20px;
 }
-.system-modules td details summary {
+.system-projects .description details > summary {
   padding: 0;
   text-transform: none;
   font-weight: normal;
   cursor: default;
+  overflow: hidden; /* Truncates descriptions if too long. */
+  text-overflow: ellipsis;
+  white-space: nowrap;
 }
-.system-modules td {
+.system-projects .description details[open] > summary {
+  overflow: visible;
+  white-space: normal;
+}
+
+.system-projects td {
   padding-left: 0;
 }
 
 @media screen and (max-width: 40em) {
-  .system-modules td.name {
+  .system-projects td.name {
     width: 20%;
   }
-  .system-modules td.description {
+  .system-projects td.description {
     width: 40%;
   }
 }
-.system-modules .requirements {
+.system-projects .requirements {
   padding: 5px 0;
   max-width: 490px;
 }
-.system-modules .links {
+.system-projects .links {
   overflow: hidden; /* prevents collapse */
 }
-.system-modules .checkbox {
+.system-projects .checkbox {
   margin: 0 5px;
 }
-.system-modules .checkbox .form-item {
+.system-projects .checkbox .form-item {
   margin-bottom: 0;
 }
 .admin-requirements,
@@ -280,6 +283,12 @@ small .admin-link:after {
   body:not(.toolbar-vertical) .system-themes-list-installed .system-themes-list__header {
     margin-top: 0;
   }
+  body:not(.toolbar-vertical) .system-themes-list-installed .theme-info {
+    margin-left: 314px;
+  }
+  [dir="rtl"] body:not(.toolbar-vertical) .system-themes-list-installed .theme-info {
+    margin-right: 314px;
+  }
   body:not(.toolbar-vertical) .system-themes-list-uninstalled .theme-selector {
     box-sizing: border-box;
     width: 31.25%;
@@ -313,6 +322,12 @@ small .admin-link:after {
   .toolbar-vertical .system-themes-list-installed .theme-info__header {
     margin-top: 0;
   }
+  .toolbar-vertical .system-themes-list-installed .theme-info {
+    margin-left: 314px;
+  }
+  [dir="rtl"] .toolbar-vertical .system-themes-list-installed .theme-info {
+    margin-right: 314px;
+  }
   .toolbar-vertical .system-themes-list-uninstalled .theme-selector {
     box-sizing: border-box;
     width: 31.25%;
@@ -327,9 +342,6 @@ small .admin-link:after {
     min-height: 170px;
   }
 }
-.system-themes-list-installed .theme-info {
-  max-width: 940px;
-}
 
 .theme-selector .incompatible {
   margin-top: 10px;
diff --git a/core/modules/system/src/Form/ModulesListForm.php b/core/modules/system/src/Form/ModulesListForm.php
index 0a52d2f..e9c373f 100644
--- a/core/modules/system/src/Form/ModulesListForm.php
+++ b/core/modules/system/src/Form/ModulesListForm.php
@@ -234,6 +234,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     // Lastly, sort all packages by title.
     uasort($form['modules'], array('\Drupal\Component\Utility\SortArray', 'sortByTitleProperty'));
 
+    $form['#attributes']['class'] = ['system-modules', 'system-projects'];
     $form['#attached']['library'][] = 'system/drupal.system.modules';
     $form['actions'] = array('#type' => 'actions');
     $form['actions']['submit'] = array(
diff --git a/core/modules/system/src/Form/ModulesListForm.php.orig b/core/modules/system/src/Form/ModulesListForm.php.orig
new file mode 100644
index 0000000..0a52d2f
--- /dev/null
+++ b/core/modules/system/src/Form/ModulesListForm.php.orig
@@ -0,0 +1,553 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Form\ModulesListForm.
+ */
+
+namespace Drupal\system\Form;
+
+use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Config\PreExistingConfigException;
+use Drupal\Core\Config\UnmetDependenciesException;
+use Drupal\Core\Controller\TitleResolverInterface;
+use Drupal\Core\Access\AccessManagerInterface;
+use Drupal\Core\Extension\Extension;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Extension\ModuleInstallerInterface;
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface;
+use Drupal\Core\Menu\MenuLinkManagerInterface;
+use Drupal\Core\Render\Element;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Routing\RouteProviderInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\user\PermissionHandlerInterface;
+use Drupal\Core\Url;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Provides module installation interface.
+ *
+ * The list of modules gets populated by module.info.yml files, which contain
+ * each module's name, description, and information about which modules it
+ * requires. See \Drupal\Core\Extension\InfoParser for info on module.info.yml
+ * descriptors.
+ */
+class ModulesListForm extends FormBase {
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $currentUser;
+
+  /**
+   * The module handler service.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The expirable key value store.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
+   */
+  protected $keyValueExpirable;
+
+  /**
+   * The title resolver.
+   *
+   * @var \Drupal\Core\Controller\TitleResolverInterface
+   */
+  protected $titleResolver;
+
+  /**
+   * The route provider.
+   *
+   * @var \Drupal\Core\Routing\RouteProviderInterface
+   */
+  protected $routeProvider;
+
+  /**
+   * The current route match.
+   *
+   * @var \Drupal\Core\Routing\RouteMatchInterface
+   */
+  protected $routeMatch;
+
+  /**
+   * The menu link manager.
+   *
+   * @var \Drupal\Core\Menu\MenuLinkManagerInterface
+   */
+  protected $menuLinkManager;
+
+  /**
+   * The module installer.
+   *
+   * @var \Drupal\Core\Extension\ModuleInstallerInterface
+   */
+  protected $moduleInstaller;
+
+  /**
+   * The permission handler.
+   *
+   * @var \Drupal\user\PermissionHandlerInterface
+   */
+  protected $permissionHandler;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('module_handler'),
+      $container->get('module_installer'),
+      $container->get('keyvalue.expirable')->get('module_list'),
+      $container->get('access_manager'),
+      $container->get('current_user'),
+      $container->get('current_route_match'),
+      $container->get('title_resolver'),
+      $container->get('router.route_provider'),
+      $container->get('plugin.manager.menu.link'),
+      $container->get('user.permissions')
+    );
+  }
+
+  /**
+   * Constructs a ModulesListForm object.
+   *
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Extension\ModuleInstallerInterface $module_installer
+   *   The module installer.
+   * @param \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface $key_value_expirable
+   *   The key value expirable factory.
+   * @param \Drupal\Core\Access\AccessManagerInterface $access_manager
+   *   Access manager.
+   * @param \Drupal\Core\Session\AccountInterface $current_user
+   *   The current user.
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The current route match.
+   * @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
+   *   The title resolver.
+   * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
+   *   The route provider.
+   * @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager
+   *   The menu link manager.
+   * @param \Drupal\user\PermissionHandlerInterface $permission_handler
+   *   The permission handler.
+   */
+  public function __construct(ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, KeyValueStoreExpirableInterface $key_value_expirable, AccessManagerInterface $access_manager, AccountInterface $current_user, RouteMatchInterface $route_match, TitleResolverInterface $title_resolver, RouteProviderInterface $route_provider, MenuLinkManagerInterface $menu_link_manager, PermissionHandlerInterface $permission_handler) {
+    $this->moduleHandler = $module_handler;
+    $this->moduleInstaller = $module_installer;
+    $this->keyValueExpirable = $key_value_expirable;
+    $this->accessManager = $access_manager;
+    $this->currentUser = $current_user;
+    $this->routeMatch = $route_match;
+    $this->titleResolver = $title_resolver;
+    $this->routeProvider = $route_provider;
+    $this->menuLinkManager = $menu_link_manager;
+    $this->permissionHandler = $permission_handler;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'system_modules';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    require_once DRUPAL_ROOT . '/core/includes/install.inc';
+    $distribution = drupal_install_profile_distribution_name();
+
+    // Include system.admin.inc so we can use the sort callbacks.
+    $this->moduleHandler->loadInclude('system', 'inc', 'system.admin');
+
+    $form['filters'] = array(
+      '#type' => 'container',
+      '#attributes' => array(
+        'class' => array('table-filter', 'js-show'),
+      ),
+    );
+
+    $form['filters']['text'] = array(
+      '#type' => 'search',
+      '#title' => $this->t('Filter modules'),
+      '#title_display' => 'invisible',
+      '#size' => 30,
+      '#placeholder' => $this->t('Filter by name or description'),
+      '#description' => $this->t('Enter a part of the module name or description'),
+      '#attributes' => array(
+        'class' => array('table-filter-text'),
+        'data-table' => '#system-modules',
+        'autocomplete' => 'off',
+      ),
+    );
+
+    // Sort all modules by their names.
+    $modules = system_rebuild_module_data();
+    uasort($modules, 'system_sort_modules_by_info_name');
+
+    // Iterate over each of the modules.
+    $form['modules']['#tree'] = TRUE;
+    foreach ($modules as $filename => $module) {
+      if (empty($module->info['hidden'])) {
+        $package = $module->info['package'];
+        $form['modules'][$package][$filename] = $this->buildRow($modules, $module, $distribution);
+      }
+    }
+
+    // Add a wrapper around every package.
+    foreach (Element::children($form['modules']) as $package) {
+      $form['modules'][$package] += array(
+        '#type' => 'details',
+        '#title' => $this->t($package),
+        '#open' => TRUE,
+        '#theme' => 'system_modules_details',
+        '#header' => array(
+          array('data' => $this->t('Installed'), 'class' => array('checkbox', 'visually-hidden')),
+          array('data' => $this->t('Name'), 'class' => array('name', 'visually-hidden')),
+          array('data' => $this->t('Description'), 'class' => array('description', 'visually-hidden', RESPONSIVE_PRIORITY_LOW)),
+        ),
+        '#attributes' => array('class' => array('package-listing')),
+        // Ensure that the "Core" package comes first.
+        '#weight' => $package == 'Core' ? -10 : NULL,
+      );
+    }
+
+    // If testing modules are shown, collapse the corresponding package by
+    // default.
+    if (isset($form['modules']['Testing'])) {
+      $form['modules']['Testing']['#open'] = FALSE;
+    }
+
+    // Lastly, sort all packages by title.
+    uasort($form['modules'], array('\Drupal\Component\Utility\SortArray', 'sortByTitleProperty'));
+
+    $form['#attached']['library'][] = 'system/drupal.system.modules';
+    $form['actions'] = array('#type' => 'actions');
+    $form['actions']['submit'] = array(
+      '#type' => 'submit',
+      '#value' => $this->t('Install'),
+      '#button_type' => 'primary',
+    );
+
+    return $form;
+  }
+
+  /**
+   * Builds a table row for the system modules page.
+   *
+   * @param array $modules
+   *   The list existing modules.
+   * @param \Drupal\Core\Extension\Extension $module
+   *   The module for which to build the form row.
+   * @param $distribution
+   *
+   * @return array
+   *   The form row for the given module.
+   */
+  protected function buildRow(array $modules, Extension $module, $distribution) {
+    // Set the basic properties.
+    $row['#required'] = array();
+    $row['#requires'] = array();
+    $row['#required_by'] = array();
+
+    $row['name']['#markup'] = $module->info['name'];
+    $row['description']['#markup'] = $this->t($module->info['description']);
+    $row['version']['#markup'] = $module->info['version'];
+
+    // Generate link for module's help page. Assume that if a hook_help()
+    // implementation exists then the module provides an overview page, rather
+    // than checking to see if the page exists, which is costly.
+    $row['links']['help'] = array();
+    if ($this->moduleHandler->moduleExists('help') && $module->status && in_array($module->getName(), $this->moduleHandler->getImplementations('help'))) {
+      $row['links']['help'] = array(
+        '#type' => 'link',
+        '#title' => $this->t('Help'),
+        '#url' => Url::fromRoute('help.page', ['name' => $module->getName()]),
+        '#options' => array('attributes' => array('class' =>  array('module-link', 'module-link-help'), 'title' => $this->t('Help'))),
+      );
+    }
+
+    // Generate link for module's permission, if the user has access to it.
+    $row['links']['permissions'] = array();
+    if ($module->status && $this->currentUser->hasPermission('administer permissions') && $this->permissionHandler->moduleProvidesPermissions($module->getName())) {
+      $row['links']['permissions'] = array(
+        '#type' => 'link',
+        '#title' => $this->t('Permissions'),
+        '#url' => Url::fromRoute('user.admin_permissions'),
+        '#options' => array('fragment' => 'module-' . $module->getName(), 'attributes' => array('class' => array('module-link', 'module-link-permissions'), 'title' => $this->t('Configure permissions'))),
+      );
+    }
+
+    // Generate link for module's configuration page, if it has one.
+    $row['links']['configure'] = array();
+    if ($module->status && isset($module->info['configure'])) {
+      $route_parameters = isset($module->info['configure_parameters']) ? $module->info['configure_parameters'] : array();
+      if ($this->accessManager->checkNamedRoute($module->info['configure'], $route_parameters, $this->currentUser)) {
+
+        $links = $this->menuLinkManager->loadLinksByRoute($module->info['configure']);
+        /** @var \Drupal\Core\Menu\MenuLinkInterface $link */
+        $link = reset($links);
+        // Most configure links have a corresponding menu link, though some just
+        // have a route.
+        if ($link) {
+          $description = $link->getDescription();
+        }
+        else {
+          $request = new Request();
+          $request->attributes->set('_route_name', $module->info['configure']);
+          $route_object = $this->routeProvider->getRouteByName($module->info['configure']);
+          $request->attributes->set('_route', $route_object);
+          $request->attributes->add($route_parameters);
+          $description = $this->titleResolver->getTitle($request, $route_object);
+        }
+
+        $row['links']['configure'] = array(
+          '#type' => 'link',
+          '#title' => $this->t('Configure'),
+          '#url' => Url::fromRoute($module->info['configure'], $route_parameters),
+          '#options' => array(
+            'attributes' => array(
+              'class' => array('module-link', 'module-link-configure'),
+              'title' => $description,
+            ),
+          ),
+        );
+      }
+    }
+
+    // Present a checkbox for installing and indicating the status of a module.
+    $row['enable'] = array(
+      '#type' => 'checkbox',
+      '#title' => $this->t('Install'),
+      '#default_value' => (bool) $module->status,
+      '#disabled' => (bool) $module->status,
+    );
+
+    // Disable the checkbox for required modules.
+    if (!empty($module->info['required'])) {
+      // Used when displaying modules that are required by the installation profile
+      $row['enable']['#disabled'] = TRUE;
+      $row['#required_by'][] = $distribution . (!empty($module->info['explanation']) ? ' ('. $module->info['explanation'] .')' : '');
+    }
+
+    // Check the compatibilities.
+    $compatible = TRUE;
+
+    // Initialize an empty array of reasons why the module is incompatible. Add
+    // each reason as a separate element of the array.
+    $reasons = array();
+
+    // Check the core compatibility.
+    if ($module->info['core'] != \Drupal::CORE_COMPATIBILITY) {
+      $compatible = FALSE;
+      $reasons[] = $this->t('This version is not compatible with Drupal !core_version and should be replaced.', array(
+        '!core_version' => \Drupal::CORE_COMPATIBILITY,
+      ));
+    }
+
+    // Ensure this module is compatible with the currently installed version of PHP.
+    if (version_compare(phpversion(), $module->info['php']) < 0) {
+      $compatible = FALSE;
+      $required = $module->info['php'] . (substr_count($module->info['php'], '.') < 2 ? '.*' : '');
+      $reasons[] = $this->t('This module requires PHP version @php_required and is incompatible with PHP version !php_version.', array(
+        '@php_required' => $required,
+        '!php_version' => phpversion(),
+      ));
+    }
+
+    // If this module is not compatible, disable the checkbox.
+    if (!$compatible) {
+      $status = implode(' ', $reasons);
+      $row['enable']['#disabled'] = TRUE;
+      $row['description']['#markup'] = $status;
+      $row['#attributes']['class'][] = 'incompatible';
+    }
+
+    // If this module requires other modules, add them to the array.
+    foreach ($module->requires as $dependency => $version) {
+      if (!isset($modules[$dependency])) {
+        $row['#requires'][$dependency] = $this->t('@module (<span class="admin-missing">missing</span>)', array('@module' => Unicode::ucfirst($dependency)));
+        $row['enable']['#disabled'] = TRUE;
+      }
+      // Only display visible modules.
+      elseif (empty($modules[$dependency]->hidden)) {
+        $name = $modules[$dependency]->info['name'];
+        // Disable the module's checkbox if it is incompatible with the
+        // dependency's version.
+        if ($incompatible_version = drupal_check_incompatibility($version, str_replace(\Drupal::CORE_COMPATIBILITY . '-', '', $modules[$dependency]->info['version']))) {
+          $row['#requires'][$dependency] = $this->t('@module (<span class="admin-missing">incompatible with</span> version @version)', array(
+            '@module' => $name . $incompatible_version,
+            '@version' => $modules[$dependency]->info['version'],
+          ));
+          $row['enable']['#disabled'] = TRUE;
+        }
+        // Disable the checkbox if the dependency is incompatible with this
+        // version of Drupal core.
+        elseif ($modules[$dependency]->info['core'] != \Drupal::CORE_COMPATIBILITY) {
+          $row['#requires'][$dependency] = $this->t('@module (<span class="admin-missing">incompatible with</span> this version of Drupal core)', array(
+            '@module' => $name,
+          ));
+          $row['enable']['#disabled'] = TRUE;
+        }
+        elseif ($modules[$dependency]->status) {
+          $row['#requires'][$dependency] = $this->t('@module', array('@module' => $name));
+        }
+        else {
+          $row['#requires'][$dependency] = $this->t('@module (<span class="admin-disabled">disabled</span>)', array('@module' => $name));
+        }
+      }
+    }
+
+    // If this module is required by other modules, list those, and then make it
+    // impossible to disable this one.
+    foreach ($module->required_by as $dependent => $version) {
+      if (isset($modules[$dependent]) && empty($modules[$dependent]->info['hidden'])) {
+        if ($modules[$dependent]->status == 1 && $module->status == 1) {
+          $row['#required_by'][$dependent] = $this->t('@module', array('@module' => $modules[$dependent]->info['name']));
+          $row['enable']['#disabled'] = TRUE;
+        }
+        else {
+          $row['#required_by'][$dependent] = $this->t('@module (<span class="admin-disabled">disabled</span>)', array('@module' => $modules[$dependent]->info['name']));
+        }
+      }
+    }
+
+    return $row;
+  }
+
+  /**
+   * Helper function for building a list of modules to install.
+   *
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @return array
+   *   An array of modules to install and their dependencies.
+   */
+  protected function buildModuleList(FormStateInterface $form_state) {
+    $packages = $form_state->getValue('modules');
+
+    // Build a list of modules to install.
+    $modules = array(
+      'install' => array(),
+      'dependencies' => array(),
+    );
+
+    // Required modules have to be installed.
+    // @todo This should really not be handled here.
+    $data = system_rebuild_module_data();
+    foreach ($data as $name => $module) {
+      if (!empty($module->required) && !$this->moduleHandler->moduleExists($name)) {
+        $modules['install'][$name] = $module->info['name'];
+      }
+    }
+
+    // First, build a list of all modules that were selected.
+    foreach ($packages as $items) {
+      foreach ($items as $name => $checkbox) {
+        if ($checkbox['enable'] && !$this->moduleHandler->moduleExists($name)) {
+          $modules['install'][$name] = $data[$name]->info['name'];
+        }
+      }
+    }
+
+    // Add all dependencies to a list.
+    while (list($module) = each($modules['install'])) {
+      foreach (array_keys($data[$module]->requires) as $dependency) {
+        if (!isset($modules['install'][$dependency]) && !$this->moduleHandler->moduleExists($dependency)) {
+          $modules['dependencies'][$module][$dependency] = $data[$dependency]->info['name'];
+          $modules['install'][$dependency] = $data[$dependency]->info['name'];
+        }
+      }
+    }
+
+    // Make sure the install API is available.
+    include_once DRUPAL_ROOT . '/core/includes/install.inc';
+
+    // Invoke hook_requirements('install'). If failures are detected, make
+    // sure the dependent modules aren't installed either.
+    foreach (array_keys($modules['install']) as $module) {
+      if (!drupal_check_module($module)) {
+        unset($modules['install'][$module]);
+        foreach (array_keys($data[$module]->required_by) as $dependent) {
+          unset($modules['install'][$dependent]);
+          unset($modules['dependencies'][$dependent]);
+        }
+      }
+    }
+
+    return $modules;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    // Retrieve a list of modules to install and their dependencies.
+    $modules = $this->buildModuleList($form_state);
+
+    // Check if we have to install any dependencies. If there is one or more
+    // dependencies that are not installed yet, redirect to the confirmation
+    // form.
+    if (!empty($modules['dependencies']) || !empty($modules['missing'])) {
+      // Write the list of changed module states into a key value store.
+      $account = $this->currentUser()->id();
+      $this->keyValueExpirable->setWithExpire($account, $modules, 60);
+
+      // Redirect to the confirmation form.
+      $form_state->setRedirect('system.modules_list_confirm');
+
+      // We can exit here because at least one modules has dependencies
+      // which we have to prompt the user for in a confirmation form.
+      return;
+    }
+
+    // Install the given modules.
+    if (!empty($modules['install'])) {
+      try {
+        $this->moduleInstaller->install(array_keys($modules['install']));
+        $module_names = array_values($modules['install']);
+        drupal_set_message($this->formatPlural(count($module_names), 'Module %name has been enabled.', '@count modules have been enabled: %names.', array(
+          '%name' => $module_names[0],
+          '%names' => implode(', ', $module_names),
+        )));
+      }
+      catch (PreExistingConfigException $e) {
+        $config_objects = $e->flattenConfigObjects($e->getConfigObjects());
+        drupal_set_message(
+          $this->formatPlural(
+            count($config_objects),
+            'Unable to install @extension, %config_names already exists in active configuration.',
+            'Unable to install @extension, %config_names already exist in active configuration.',
+            array(
+              '%config_names' => implode(', ', $config_objects),
+              '@extension' => $modules['install'][$e->getExtension()]
+            )),
+          'error'
+        );
+        return;
+      }
+      catch (UnmetDependenciesException $e) {
+        drupal_set_message(
+          $e->getTranslatedMessage($this->getStringTranslation(), $modules['install'][$e->getExtension()]),
+          'error'
+        );
+        return;
+      }
+    }
+  }
+
+}
diff --git a/core/modules/system/src/Tests/System/ThemeTest.php b/core/modules/system/src/Tests/System/ThemeTest.php
index f4e87e1..c793198 100644
--- a/core/modules/system/src/Tests/System/ThemeTest.php
+++ b/core/modules/system/src/Tests/System/ThemeTest.php
@@ -360,7 +360,8 @@ function testInstallAndSetAsDefault() {
     $version = $themes['bartik']->info['version'];
 
     // Confirm Bartik is indicated as the default theme.
-    $this->assertTextPattern('/Bartik ' . preg_quote($version) . '\s{2,}\(default theme\)/');
+    $this->assertTextPattern('/Bartik\s{2,}\(default theme\)/');
+    $this->assertTextPattern('/Version: ' . preg_quote($version) . '/');
   }
 
 }
diff --git a/core/modules/system/src/Tests/System/ThemeTest.php.orig b/core/modules/system/src/Tests/System/ThemeTest.php.orig
new file mode 100644
index 0000000..f4e87e1
--- /dev/null
+++ b/core/modules/system/src/Tests/System/ThemeTest.php.orig
@@ -0,0 +1,366 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\System\ThemeTest.
+ */
+
+namespace Drupal\system\Tests\System;
+
+use Drupal\Core\StreamWrapper\PublicStream;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests the theme interface functionality by enabling and switching themes, and
+ * using an administration theme.
+ *
+ * @group system
+ */
+class ThemeTest extends WebTestBase {
+
+  /**
+   * A user with administrative permissions.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $adminUser;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['node', 'block', 'file'];
+
+  protected function setUp() {
+    parent::setUp();
+
+    $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
+
+    $this->adminUser = $this->drupalCreateUser(array('access administration pages', 'view the administration theme', 'administer themes', 'bypass node access', 'administer blocks'));
+    $this->drupalLogin($this->adminUser);
+    $this->node = $this->drupalCreateNode();
+    $this->drupalPlaceBlock('local_tasks_block');
+  }
+
+  /**
+   * Test the theme settings form.
+   */
+  function testThemeSettings() {
+    // Ensure invalid theme settings form URLs return a proper 404.
+    $this->drupalGet('admin/appearance/settings/bartik');
+    $this->assertResponse(404, 'The theme settings form URL for a uninstalled theme could not be found.');
+    $this->drupalGet('admin/appearance/settings/' . $this->randomMachineName());
+    $this->assertResponse(404, 'The theme settings form URL for a non-existent theme could not be found.');
+
+    // Specify a filesystem path to be used for the logo.
+    $file = current($this->drupalGetTestFiles('image'));
+    $file_relative = strtr($file->uri, array('public:/' => PublicStream::basePath()));
+    $default_theme_path = 'core/themes/classy';
+
+    $supported_paths = array(
+      // Raw stream wrapper URI.
+      $file->uri => array(
+        'form' => file_uri_target($file->uri),
+        'src' => file_create_url($file->uri),
+      ),
+      // Relative path within the public filesystem.
+      file_uri_target($file->uri) => array(
+        'form' => file_uri_target($file->uri),
+        'src' => file_create_url($file->uri),
+      ),
+      // Relative path to a public file.
+      $file_relative => array(
+        'form' => $file_relative,
+        'src' => file_create_url($file->uri),
+      ),
+      // Relative path to an arbitrary file.
+      'core/misc/druplicon.png' => array(
+        'form' => 'core/misc/druplicon.png',
+        'src' => $GLOBALS['base_url'] . '/' . 'core/misc/druplicon.png',
+      ),
+      // Relative path to a file in a theme.
+      $default_theme_path . '/logo.svg' => array(
+        'form' => $default_theme_path . '/logo.svg',
+        'src' => $GLOBALS['base_url'] . '/' . $default_theme_path . '/logo.svg',
+      ),
+    );
+    foreach ($supported_paths as $input => $expected) {
+      $edit = array(
+        'default_logo' => FALSE,
+        'logo_path' => $input,
+      );
+      $this->drupalPostForm('admin/appearance/settings', $edit, t('Save configuration'));
+      $this->assertNoText('The custom logo path is invalid.');
+      $this->assertFieldByName('logo_path', $expected['form']);
+
+      // Verify logo path examples.
+      $elements = $this->xpath('//div[contains(@class, :item)]/div[@class=:description]/code', array(
+        ':item' => 'form-item-logo-path',
+        ':description' => 'description',
+      ));
+      // Expected default values (if all else fails).
+      $implicit_public_file = 'logo.svg';
+      $explicit_file = 'public://logo.svg';
+      $local_file = $default_theme_path . '/logo.svg';
+      // Adjust for fully qualified stream wrapper URI in public filesystem.
+      if (file_uri_scheme($input) == 'public') {
+        $implicit_public_file = file_uri_target($input);
+        $explicit_file = $input;
+        $local_file = strtr($input, array('public:/' => PublicStream::basePath()));
+      }
+      // Adjust for fully qualified stream wrapper URI elsewhere.
+      elseif (file_uri_scheme($input) !== FALSE) {
+        $explicit_file = $input;
+      }
+      // Adjust for relative path within public filesystem.
+      elseif ($input == file_uri_target($file->uri)) {
+        $implicit_public_file = $input;
+        $explicit_file = 'public://' . $input;
+        $local_file = PublicStream::basePath() . '/' . $input;
+      }
+      $this->assertEqual((string) $elements[0], $implicit_public_file);
+      $this->assertEqual((string) $elements[1], $explicit_file);
+      $this->assertEqual((string) $elements[2], $local_file);
+
+      // Verify the actual 'src' attribute of the logo being output.
+      $this->drupalGet('');
+      $elements = $this->xpath('//header/a[@rel=:rel]/img', array(
+          ':rel' => 'home',
+        )
+      );
+      $this->assertEqual((string) $elements[0]['src'], $expected['src']);
+    }
+    $unsupported_paths = array(
+      // Stream wrapper URI to non-existing file.
+      'public://whatever.png',
+      'private://whatever.png',
+      'temporary://whatever.png',
+      // Bogus stream wrapper URIs.
+      'public:/whatever.png',
+      '://whatever.png',
+      ':whatever.png',
+      'public://',
+      // Relative path within the public filesystem to non-existing file.
+      'whatever.png',
+      // Relative path to non-existing file in public filesystem.
+      PublicStream::basePath() . '/whatever.png',
+      // Semi-absolute path to non-existing file in public filesystem.
+      '/' . PublicStream::basePath() . '/whatever.png',
+      // Relative path to arbitrary non-existing file.
+      'core/misc/whatever.png',
+      // Semi-absolute path to arbitrary non-existing file.
+      '/core/misc/whatever.png',
+      // Absolute paths to any local file (even if it exists).
+      drupal_realpath($file->uri),
+    );
+    $this->drupalGet('admin/appearance/settings');
+    foreach ($unsupported_paths as $path) {
+      $edit = array(
+        'default_logo' => FALSE,
+        'logo_path' => $path,
+      );
+      $this->drupalPostForm(NULL, $edit, t('Save configuration'));
+      $this->assertText('The custom logo path is invalid.');
+    }
+
+    // Upload a file to use for the logo.
+    $edit = array(
+      'default_logo' => FALSE,
+      'logo_path' => '',
+      'files[logo_upload]' => drupal_realpath($file->uri),
+    );
+    $this->drupalPostForm('admin/appearance/settings', $edit, t('Save configuration'));
+
+    $fields = $this->xpath($this->constructFieldXpath('name', 'logo_path'));
+    $uploaded_filename = 'public://' . $fields[0]['value'];
+
+    $this->drupalGet('');
+    $elements = $this->xpath('//header/a[@rel=:rel]/img', array(
+        ':rel' => 'home',
+      )
+    );
+    $this->assertEqual($elements[0]['src'], file_create_url($uploaded_filename));
+  }
+
+  /**
+   * Test the administration theme functionality.
+   */
+  function testAdministrationTheme() {
+    $this->container->get('theme_handler')->install(array('seven'));
+
+    // Install an administration theme and show it on the node admin pages.
+    $edit = array(
+      'admin_theme' => 'seven',
+      'use_admin_theme' => TRUE,
+    );
+    $this->drupalPostForm('admin/appearance', $edit, t('Save configuration'));
+
+    $this->drupalGet('admin/config');
+    $this->assertRaw('core/themes/seven', 'Administration theme used on an administration page.');
+
+    $this->drupalGet('node/' . $this->node->id());
+    $this->assertRaw('core/themes/classy', 'Site default theme used on node page.');
+
+    $this->drupalGet('node/add');
+    $this->assertRaw('core/themes/seven', 'Administration theme used on the add content page.');
+
+    $this->drupalGet('node/' . $this->node->id() . '/edit');
+    $this->assertRaw('core/themes/seven', 'Administration theme used on the edit content page.');
+
+    // Disable the admin theme on the node admin pages.
+    $edit = array(
+      'use_admin_theme' => FALSE,
+    );
+    $this->drupalPostForm('admin/appearance', $edit, t('Save configuration'));
+
+    $this->drupalGet('admin/config');
+    $this->assertRaw('core/themes/seven', 'Administration theme used on an administration page.');
+
+    // Ensure that the admin theme is also visible on the 403 page.
+    $normal_user = $this->drupalCreateUser(['view the administration theme']);
+    $this->drupalLogin($normal_user);
+    $this->drupalGet('admin/config');
+    $this->assertResponse(403);
+    $this->assertRaw('core/themes/seven', 'Administration theme used on an administration page.');
+    $this->drupalLogin($this->adminUser);
+
+    $this->drupalGet('node/add');
+    $this->assertRaw('core/themes/classy', 'Site default theme used on the add content page.');
+
+    // Reset to the default theme settings.
+    $edit = array(
+      'admin_theme' => '0',
+      'use_admin_theme' => FALSE,
+    );
+    $this->drupalPostForm('admin/appearance', $edit, t('Save configuration'));
+
+    $this->drupalGet('admin');
+    $this->assertRaw('core/themes/classy', 'Site default theme used on administration page.');
+
+    $this->drupalGet('node/add');
+    $this->assertRaw('core/themes/classy', 'Site default theme used on the add content page.');
+  }
+
+  /**
+   * Test switching the default theme.
+   */
+  function testSwitchDefaultTheme() {
+    // Install Bartik and set it as the default theme.
+    \Drupal::service('theme_handler')->install(array('bartik'));
+    $this->drupalGet('admin/appearance');
+    $this->clickLink(t('Set as default'));
+    $this->assertEqual($this->config('system.theme')->get('default'), 'bartik');
+
+    // Test the default theme on the secondary links (blocks admin page).
+    $this->drupalGet('admin/structure/block');
+    $this->assertText('Bartik(' . t('active tab') . ')', 'Default local task on blocks admin page is the default theme.');
+    // Switch back to Stark and test again to test that the menu cache is cleared.
+    $this->drupalGet('admin/appearance');
+    // Classy is the first 'Set as default' link.
+    $this->clickLink(t('Set as default'), 0);
+    $this->drupalGet('admin/structure/block');
+    $this->assertText('Classy(' . t('active tab') . ')', 'Default local task on blocks admin page has changed.');
+  }
+
+  /**
+   * Test themes can't be installed when the base theme or engine is missing.
+   */
+  function testInvalidTheme() {
+    // theme_page_test_system_info_alter() un-hides all hidden themes.
+    $this->container->get('module_installer')->install(array('theme_page_test'));
+    // Clear the system_list() and theme listing cache to pick up the change.
+    $this->container->get('theme_handler')->reset();
+    $this->drupalGet('admin/appearance');
+    $this->assertText(t('This theme requires the base theme @base_theme to operate correctly.', array('@base_theme' => 'not_real_test_basetheme')));
+    $this->assertText(t('This theme requires the theme engine @theme_engine to operate correctly.', array('@theme_engine' => 'not_real_engine')));
+    // Check for the error text of a theme with the wrong core version.
+    $this->assertText("This theme is not compatible with Drupal 8.x. Check that the .info.yml file contains the correct 'core' value.");
+    // Check for the error text of a theme without a content region.
+    $this->assertText("This theme is missing a 'content' region.");
+  }
+
+  /**
+   * Test uninstalling of themes works.
+   */
+  function testUninstallingThemes() {
+    // Install Bartik and set it as the default theme.
+    \Drupal::service('theme_handler')->install(array('bartik'));
+    // Set up seven as the admin theme.
+    \Drupal::service('theme_handler')->install(array('seven'));
+    $edit = array(
+      'admin_theme' => 'seven',
+      'use_admin_theme' => TRUE,
+    );
+    $this->drupalPostForm('admin/appearance', $edit, t('Save configuration'));
+    $this->drupalGet('admin/appearance');
+    $this->clickLink(t('Set as default'));
+
+    // Check that seven cannot be uninstalled as it is the admin theme.
+    $this->assertNoRaw('Uninstall Seven theme', 'A link to uninstall the Seven theme does not appear on the theme settings page.');
+    // Check that bartik cannot be uninstalled as it is the default theme.
+    $this->assertNoRaw('Uninstall Bartik theme', 'A link to uninstall the Bartik theme does not appear on the theme settings page.');
+    // Check that the classy theme cannot be uninstalled as it is a base theme
+    // of seven and bartik.
+    $this->assertNoRaw('Uninstall Classy theme', 'A link to uninstall the Classy theme does not appear on the theme settings page.');
+
+    // Install Stark and set it as the default theme.
+    \Drupal::service('theme_handler')->install(array('stark'));
+
+    $edit = array(
+      'admin_theme' => 'stark',
+      'use_admin_theme' => TRUE,
+    );
+    $this->drupalPostForm('admin/appearance', $edit, t('Save configuration'));
+
+    // Check that seven can be uninstalled now.
+    $this->assertRaw('Uninstall Seven theme', 'A link to uninstall the Seven theme does appear on the theme settings page.');
+    // Check that the classy theme still cannot be uninstalled as it is a
+    // base theme of bartik.
+    $this->assertNoRaw('Uninstall Classy theme', 'A link to uninstall the Classy theme does not appear on the theme settings page.');
+
+    // Change the default theme to stark, stark is third in the list.
+    $this->clickLink(t('Set as default'), 2);
+
+    // Check that bartik can be uninstalled now.
+    $this->assertRaw('Uninstall Bartik theme', 'A link to uninstall the Bartik theme does appear on the theme settings page.');
+
+    // Check that the classy theme still can't be uninstalled as neither of it's
+    // base themes have been.
+    $this->assertNoRaw('Uninstall Classy theme', 'A link to uninstall the Classy theme does not appear on the theme settings page.');
+
+    // Uninstall each of the three themes starting with Bartik.
+    $this->clickLink(t('Uninstall'));
+    $this->assertRaw('The <em class="placeholder">Bartik</em> theme has been uninstalled');
+    // Seven is the second in the list.
+    $this->clickLink(t('Uninstall'));
+    $this->assertRaw('The <em class="placeholder">Seven</em> theme has been uninstalled');
+    // Now uninstall classy.
+    $this->clickLink(t('Uninstall'));
+    $this->assertRaw('The <em class="placeholder">Classy</em> theme has been uninstalled');
+  }
+
+  /**
+   * Tests installing a theme and setting it as default.
+   */
+  function testInstallAndSetAsDefault() {
+    $this->drupalGet('admin/appearance');
+    // Bartik is uninstalled in the test profile and has the second "Install and
+    // set as default" link.
+    $this->clickLink(t('Install and set as default'), 1);
+    // Test the confirmation message.
+    $this->assertText('Bartik is now the default theme.');
+    // Make sure Bartik is now set as the default theme in config.
+    $this->assertEqual($this->config('system.theme')->get('default'), 'bartik');
+
+    // This checks for a regression. See https://www.drupal.org/node/2498691.
+    $this->assertNoText('The bartik theme was not found.');
+
+    $themes = \Drupal::service('theme_handler')->rebuildThemeData();
+    $version = $themes['bartik']->info['version'];
+
+    // Confirm Bartik is indicated as the default theme.
+    $this->assertTextPattern('/Bartik ' . preg_quote($version) . '\s{2,}\(default theme\)/');
+  }
+
+}
diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc
index 021e2dc..77fa465 100644
--- a/core/modules/system/system.admin.inc
+++ b/core/modules/system/system.admin.inc
@@ -381,6 +381,15 @@ function template_preprocess_system_themes_page(&$variables) {
   $groups = array();
   $theme_groups = $variables['theme_groups'];
   $variables['attributes']['id'] = 'system-themes-page';
+  $variables['attributes']['class'] = ['system-themes', 'system-projects'];
+  $theme_infos = [];
+
+  // Build a list of theme names for all known themes
+  foreach ($variables['theme_group_titles'] as $state => $title) {
+    foreach ($theme_groups[$state] as $theme) {
+      $theme_infos[$theme->getName()] = $theme->info['name'];
+    }
+  }
 
   foreach ($variables['theme_group_titles'] as $state => $title) {
     if (!count($theme_groups[$state])) {
@@ -417,12 +426,36 @@ function template_preprocess_system_themes_page(&$variables) {
         );
       }
 
+      $version = isset($theme->info['version']) ? $theme->info['version'] : '';
+
+      $requires = [];
+      foreach (array_keys($theme->requires) as $key) {
+        $requires[$key] = isset($theme_infos[$key]) ? $theme_infos[$key] : $key;
+      }
+
+      $required_by = [];
+      foreach (array_keys($theme->required_by) as $key) {
+        $required_by[$key] = isset($theme_infos[$key]) ? $theme_infos[$key] : $key;
+      }
+
+      $description = [
+        '#theme' => 'system_project_details__theme',
+        '#machine_name' => $theme->getName(),
+        '#version' => $version,
+        '#requires' => $requires,
+        '#required_by' => $required_by,
+      ];
+
       // Localize the theme description.
-      $current_theme['description'] = t($theme->info['description']);
+      $current_theme['description'] = [
+        '#theme' => 'details',
+        '#title' => t($theme->info['description']),
+        '#description' => $description,
+      ];
 
       $current_theme['attributes'] = new Attribute();
       $current_theme['name'] = $theme->info['name'];
-      $current_theme['version'] = isset($theme->info['version']) ? $theme->info['version'] : '';
+      $current_theme['version'] = $version;
       $current_theme['notes'] = $theme->notes;
       $current_theme['is_default'] = $theme->is_default;
       $current_theme['is_admin'] = $theme->is_admin;
@@ -461,5 +494,4 @@ function template_preprocess_system_themes_page(&$variables) {
     $groups[] = $theme_group;
   }
   $variables['theme_groups'] = $groups;
-}
-
+}
\ No newline at end of file
diff --git a/core/modules/system/system.admin.inc.orig b/core/modules/system/system.admin.inc.orig
new file mode 100644
index 0000000..021e2dc
--- /dev/null
+++ b/core/modules/system/system.admin.inc.orig
@@ -0,0 +1,465 @@
+<?php
+
+/**
+ * @file
+ * Admin page callbacks for the system module.
+ */
+
+use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Component\Utility\Xss;
+use Drupal\Component\Utility\Html;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Extension\Extension;
+use Drupal\Core\Render\Element;
+use Drupal\Core\Template\Attribute;
+
+/**
+ * Recursively check compatibility.
+ *
+ * @param $incompatible
+ *   An associative array which at the end of the check contains all
+ *   incompatible files as the keys, their values being TRUE.
+ * @param $files
+ *   The set of files that will be tested.
+ * @param \Drupal\Core\Extension\Extension $file
+ *   The file at which the check starts.
+ * @return
+ *   Returns TRUE if an incompatible file is found, NULL (no return value)
+ *   otherwise.
+ */
+function _system_is_incompatible(&$incompatible, $files, Extension $file) {
+  if (isset($incompatible[$file->getName()])) {
+    return TRUE;
+  }
+  // Recursively traverse required modules, looking for incompatible modules.
+  foreach ($file->requires as $requires) {
+    if (isset($files[$requires]) && _system_is_incompatible($incompatible, $files, $files[$requires])) {
+      $incompatible[$file->getName()] = TRUE;
+      return TRUE;
+    }
+  }
+}
+
+/**
+ * Prepares variables for administrative content block templates.
+ *
+ * Default template: admin-block-content.html.twig.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - content: An array containing information about the block. Each element
+ *     of the array represents an administrative menu item, and must at least
+ *     contain the keys 'title', 'link_path', and 'localized_options', which are
+ *     passed to l(). A 'description' key may also be provided.
+ */
+function template_preprocess_admin_block_content(&$variables) {
+  if (!empty($variables['content'])) {
+    $variables['compact'] = system_admin_compact_mode();
+    foreach ($variables['content'] as $key => $item) {
+      $variables['content'][$key]['link'] = \Drupal::l($item['title'], $item['url']);
+      if (!$variables['compact'] && isset($item['description'])) {
+        $variables['content'][$key]['description'] = ['#markup' => $item['description']];
+      }
+      else {
+        $variables['content'][$key]['description'] = FALSE;
+      }
+    }
+  }
+}
+
+/**
+ * Prepares variables for administrative index page templates.
+ *
+ * Default template: admin-page.html.twig.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - blocks: An array of blocks to display. Each array should include a
+ *     'title', a 'description', a formatted 'content' and a 'position' which
+ *     will control which container it will be in. This is usually 'left' or
+ *     'right'.
+ */
+function template_preprocess_admin_page(&$variables) {
+  $variables['system_compact_link'] = array(
+    '#type' => 'system_compact_link',
+  );
+  $variables['containers'] = array();
+  $stripe = 0;
+  foreach ($variables['blocks'] as $block) {
+    if (!empty($block['content']['#content'])) {
+      if (empty($block['position'])) {
+        // Perform automatic striping.
+        $block['position'] = ++$stripe % 2 ? 'left' : 'right';
+      }
+      $variables['containers'][$block['position']]['blocks'][] = array(
+        '#theme' => 'admin_block',
+        '#block' => $block,
+      );
+    }
+  }
+}
+
+/**
+ * Prepares variables for admin index templates.
+ *
+ * Default template: system-admin-index.html.twig.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - menu_items: An array of modules to be displayed.
+ */
+function template_preprocess_system_admin_index(&$variables) {
+  $variables['system_compact_link'] = array(
+    '#type' => 'system_compact_link',
+  );
+  $variables['containers'] = array();
+  $stripe = 0;
+  // Iterate over all modules.
+  foreach ($variables['menu_items'] as $module => $block) {
+    list($description, $items) = $block;
+    $position = ++$stripe % 2 ? 'left' : 'right';
+    // Output links.
+    if (count($items)) {
+      $variables['containers'][$position][] = array(
+        '#theme' => 'admin_block',
+        '#block' => array(
+          'position' => $position,
+          'title' => $module,
+          'content' => array(
+            '#theme' => 'admin_block_content',
+            '#content' => $items,
+          ),
+          'description' => t($description),
+        ),
+      );
+    }
+  }
+}
+
+/**
+ * Prepares variables for status report template.
+ *
+ * Default template: status-report.html.twig.
+ *
+ * This theme function is dependent on install.inc being loaded, because
+ * that's where the constants are defined.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - requirements: An array of requirements/status items. Each requirement
+ *     is an associative array containing the following elements:
+ *     - title: The name of the requirement.
+ *     - value: (optional) The current value (version, time, level, etc).
+ *     - description: (optional) The description of the requirement.
+ *     - severity: (optional) The requirement's result/severity level, one of:
+ *       - REQUIREMENT_INFO: Status information.
+ *       - REQUIREMENT_OK: The requirement is satisfied.
+ *       - REQUIREMENT_WARNING: The requirement failed with a warning.
+ *       - REQUIREMENT_ERROR: The requirement failed with an error.
+ */
+function template_preprocess_status_report(&$variables) {
+  $severities = array(
+    REQUIREMENT_INFO => array(
+      'title' => t('Info'),
+      'status' => 'info',
+    ),
+    REQUIREMENT_OK => array(
+      'title' => t('OK'),
+      'status' => 'ok',
+    ),
+    REQUIREMENT_WARNING => array(
+      'title' => t('Warning'),
+      'status' => 'warning',
+    ),
+    REQUIREMENT_ERROR => array(
+      'title' => t('Error'),
+      'status' => 'error',
+    ),
+  );
+
+  foreach ($variables['requirements'] as $i => $requirement) {
+    // Always use the explicit requirement severity, if defined. Otherwise,
+    // default to REQUIREMENT_OK in the installer to visually confirm that
+    // installation requirements are met. And default to REQUIREMENT_INFO to
+    // denote neutral information without special visualization.
+    if (isset($requirement['severity'])) {
+      $severity = $severities[(int) $requirement['severity']];
+    }
+    elseif (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'install') {
+      $severity = $severities[REQUIREMENT_OK];
+    }
+    else {
+      $severity = $severities[REQUIREMENT_INFO];
+    }
+    $variables['requirements'][$i]['severity_title'] = $severity['title'];
+    $variables['requirements'][$i]['severity_status'] = $severity['status'];
+  }
+}
+
+/**
+ * Returns HTML for the modules form.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - form: A render element representing the form.
+ *
+ * @ingroup themeable
+ */
+function theme_system_modules_details($variables) {
+  $form = $variables['form'];
+
+  // Individual table headers.
+  $rows = array();
+  // Iterate through all the modules, which are children of this element.
+  foreach (Element::children($form) as $key) {
+    // Stick the key into $module for easier access.
+    $module = $form[$key];
+    // Create the row for the table.
+    $row = array();
+    // Add the checkbox into the first cell.
+    unset($module['enable']['#title']);
+    $module['#requires'] = array_filter($module['#requires']);
+    $module['#required_by'] = array_filter($module['#required_by']);
+
+    $requires = !empty($module['#requires']);
+    $required_by = !empty($module['#required_by']);
+    $version = !empty($module['version']['#markup']);
+
+    $row[] = array('class' => array('checkbox'), 'data' => drupal_render($module['enable']));
+
+    // Add the module label and expand/collapse functionality.
+    $id = Html::getUniqueId('module-' . $key);
+    $col2 = [
+      '#type' => 'inline_template',
+      '#template' => '<label id="{{ id }}" for="{{ enable_id }}" class="module-name table-filter-text-source">{{ module_name }}</label>',
+      '#context' => [
+        'id' => $id,
+        'enable_id' => $module['enable']['#id'],
+        'module_name' => $module['name'],
+      ],
+    ];
+    $row[] = ['class' => ['module'], 'data' => $col2];
+
+    // Add the description, along with any modules it requires.
+    $description = '';
+    $description .= '<div class="requirements">';
+    $description .= '<div class="admin-requirements">' . t('Machine name: !machine-name', array('!machine-name' => '<span dir="ltr" class="table-filter-text-source">' . $key . '</span>')) . '</div>';
+    if ($version) {
+      $description .= '<div class="admin-requirements">' . t('Version: !module-version', array('!module-version' => drupal_render($module['version']))) . '</div>';
+    }
+    if ($requires) {
+      $description .= '<div class="admin-requirements">' . t('Requires: !module-list', array('!module-list' => implode(', ', $module['#requires']))) . '</div>';
+    }
+    if ($required_by) {
+      $description .= '<div class="admin-requirements">' . t('Required by: !module-list', array('!module-list' => implode(', ', $module['#required_by']))) . '</div>';
+    }
+    $description .= '</div>';
+    $links = '';
+    foreach (array('help', 'permissions', 'configure') as $link_type) {
+      $links .= drupal_render($module['links'][$link_type]);
+    }
+    if ($links) {
+      $description .= '  <div class="links">';
+      $description .= $links;
+      $description .= '</div>';
+    }
+    $title = [
+      '#type' => 'inline_template',
+      '#template' => '<span class="text module-description">{{ module_description }}</span>',
+      '#context' => ['module_description' => $module['description']],
+    ];
+    $details = array(
+      '#type' => 'details',
+      '#title' => $title,
+      '#attributes' => array('id' => $module['enable']['#id'] . '-description'),
+      '#description' => $description,
+    );
+    $row[] = ['class' => ['description', 'expand'], 'data' => $details];
+
+    $rows[] = $module['#attributes'] + array('data' => $row);
+  }
+
+  $table = array(
+    '#type' => 'table',
+    '#header' => $form['#header'],
+    '#rows' => $rows,
+  );
+  return drupal_render($table);
+}
+
+/**
+ * Returns HTML for a table of currently disabled modules.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - form: A render element representing the form.
+ *
+ * @ingroup themeable
+ */
+function theme_system_modules_uninstall($variables) {
+  $form = $variables['form'];
+
+  // No theming for the confirm form.
+  if (isset($form['confirm'])) {
+    return drupal_render($form);
+  }
+
+  // Table headers.
+  $header = array(t('Uninstall'),
+    t('Name'),
+    t('Description'),
+  );
+
+  // Display table.
+  $rows = array();
+  foreach (Element::children($form['modules']) as $module) {
+    $disabled_header = '';
+    $disabled_reasons = '';
+    // Add the modules requiring the module in question as a validation reason.
+    if (!empty($form['modules'][$module]['#required_by'])) {
+      $form['modules'][$module]['#validation_reasons'][] = \Drupal::translation()->translate('Required by: @modules', array('@modules' => implode(', ',$form['modules'][$module]['#required_by'])));
+    }
+    if (!empty($form['modules'][$module]['#validation_reasons'])) {
+      $disabled_reasons = [
+        '#theme' => 'item_list',
+        '#items' => $form['modules'][$module]['#validation_reasons'],
+      ];
+      $disabled_reasons = drupal_render($disabled_reasons);
+      $disabled_header = \Drupal::translation()->formatPlural(count($form['modules'][$module]['#validation_reasons']),
+        'The following reason prevents @module from being uninstalled:',
+        'The following reasons prevents @module from being uninstalled:',
+        array('@module' => $form['modules'][$module]['#module_name']));
+    }
+    $rows[] = array(
+      array('data' => drupal_render($form['uninstall'][$module]), 'align' => 'center'),
+      array(
+        'data' => array(
+          '#type' => 'inline_template',
+          '#template' => '<label for="{{ module_id }}" class="module-name table-filter-text-source">{{ module_name }}</label>',
+          '#context' => array('module_id' => $form['uninstall'][$module]['#id'], 'module_name' => drupal_render($form['modules'][$module]['name'])),
+        )
+      ),
+      array(
+        'data' => array(
+          '#type' => 'inline_template',
+          '#template' => '<span class="text module-description">{{ module_description }}</span>{% if disabled_header is not empty %}<div class="admin-requirements">{{ disabled_header }}{{ disabled_reasons }}</div>{% endif %}',
+          '#context' => array(
+            'module_description' => drupal_render($form['modules'][$module]['description']),
+            'disabled_header' => $disabled_header,
+            'disabled_reasons' => $disabled_reasons,
+          ),
+        ),
+        'class' => array('description'),
+      ),
+    );
+  }
+
+  $table = array(
+    '#type' => 'table',
+    '#header' => $header,
+    '#rows' => $rows,
+    '#empty' => t('No modules are available to uninstall.'),
+  );
+  $output = drupal_render($form['filters']);
+  $output .= drupal_render($table);
+  $output .= drupal_render_children($form);
+
+  return $output;
+}
+
+/**
+ * Prepares variables for appearance page templates.
+ *
+ * Default template: system-themes-page.html.twig.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - theme_groups: An associative array containing groups of themes.
+ *   - theme_group_titles: An associative array containing titles of themes.
+ */
+function template_preprocess_system_themes_page(&$variables) {
+  $groups = array();
+  $theme_groups = $variables['theme_groups'];
+  $variables['attributes']['id'] = 'system-themes-page';
+
+  foreach ($variables['theme_group_titles'] as $state => $title) {
+    if (!count($theme_groups[$state])) {
+      // Skip this group of themes if no theme is there.
+      continue;
+    }
+    // Start new theme group.
+    $theme_group = array();
+    $theme_group['state'] = $state;
+    $theme_group['title'] = $title;
+    $theme_group['themes'] = array();
+    $theme_group['attributes'] = new Attribute();
+
+    foreach ($theme_groups[$state] as $theme) {
+      $current_theme = array();
+
+      // Screenshot depicting the theme.
+      if ($theme->screenshot) {
+        $current_theme['screenshot'] = array(
+          '#theme' => 'image',
+          '#uri' => $theme->screenshot['uri'],
+          '#alt' => $theme->screenshot['alt'],
+          '#title' => $theme->screenshot['title'],
+          '#attributes' => $theme->screenshot['attributes'],
+        );
+      }
+      else {
+        $current_theme['screenshot'] = array(
+          '#theme' => 'image',
+          '#uri' => drupal_get_path('module', 'system') . '/images/no_screenshot.png',
+          '#alt' => t('No screenshot'),
+          '#title' => t('No screenshot'),
+          '#attributes' => new Attribute(array('class' => array('no-screenshot'))),
+        );
+      }
+
+      // Localize the theme description.
+      $current_theme['description'] = t($theme->info['description']);
+
+      $current_theme['attributes'] = new Attribute();
+      $current_theme['name'] = $theme->info['name'];
+      $current_theme['version'] = isset($theme->info['version']) ? $theme->info['version'] : '';
+      $current_theme['notes'] = $theme->notes;
+      $current_theme['is_default'] = $theme->is_default;
+      $current_theme['is_admin'] = $theme->is_admin;
+
+      // Make sure to provide feedback on compatibility.
+      $current_theme['incompatible'] = '';
+      if (!empty($theme->incompatible_core)) {
+        $current_theme['incompatible'] = t("This theme is not compatible with Drupal @core_version. Check that the .info.yml file contains the correct 'core' value.", ['@core_version' => \Drupal::CORE_COMPATIBILITY]);
+      }
+      elseif (!empty($theme->incompatible_region)) {
+        $current_theme['incompatible'] = t("This theme is missing a 'content' region.");
+      }
+      elseif (!empty($theme->incompatible_php)) {
+        if (substr_count($theme->info['php'], '.') < 2) {
+          $theme->info['php'] .= '.*';
+        }
+        $current_theme['incompatible'] = t('This theme requires PHP version @php_required and is incompatible with PHP version !php_version.', array('@php_required' => $theme->info['php'], '!php_version' => phpversion()));
+      }
+      elseif (!empty($theme->incompatible_base)) {
+        $current_theme['incompatible'] = t('This theme requires the base theme @base_theme to operate correctly.', array('@base_theme' => $theme->info['base theme']));
+      }
+      elseif (!empty($theme->incompatible_engine)) {
+        $current_theme['incompatible'] = t('This theme requires the theme engine @theme_engine to operate correctly.', array('@theme_engine' => $theme->info['engine']));
+      }
+
+      // Build operation links.
+      $current_theme['operations'] = array(
+        '#theme' => 'links',
+        '#links' => $theme->operations,
+        '#attributes' => array(
+          'class' => array('operations', 'clearfix'),
+        ),
+      );
+      $theme_group['themes'][] = $current_theme;
+    }
+    $groups[] = $theme_group;
+  }
+  $variables['theme_groups'] = $groups;
+}
+
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index c12a2d4..6256152 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -213,6 +213,14 @@ function system_theme() {
       'variables' => array('menu_items' => NULL),
       'file' => 'system.admin.inc',
     ),
+    'system_project_details' => [
+      'variables' => [
+        'machine_name' => [],
+        'version' => '',
+        'requires' => [],
+        'required_by' => [],
+      ],
+    ],
   ));
 }
 
diff --git a/core/modules/system/system.module.orig b/core/modules/system/system.module.orig
new file mode 100644
index 0000000..c12a2d4
--- /dev/null
+++ b/core/modules/system/system.module.orig
@@ -0,0 +1,1429 @@
+<?php
+
+/**
+ * @file
+ * Configuration system that lets administrators modify the workings of the site.
+ */
+
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Asset\AttachedAssetsInterface;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Extension\Extension;
+use Drupal\Core\Extension\ExtensionDiscovery;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\KeyValueStore\KeyValueDatabaseExpirableFactory;
+use Drupal\Core\PageCache\RequestPolicyInterface;
+use Drupal\Core\PhpStorage\PhpStorageFactory;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Menu\MenuTreeParameters;
+use Drupal\Core\Extension\ModuleHandler;
+use Drupal\Core\Url;
+use Drupal\Core\Block\BlockPluginInterface;
+use Drupal\user\UserInterface;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use GuzzleHttp\Exception\RequestException;
+
+/**
+ * New users will be set to the default time zone at registration.
+ */
+const DRUPAL_USER_TIMEZONE_DEFAULT = 0;
+
+/**
+ * New users will get an empty time zone at registration.
+ */
+const DRUPAL_USER_TIMEZONE_EMPTY = 1;
+
+/**
+ * New users will select their own timezone at registration.
+ */
+const DRUPAL_USER_TIMEZONE_SELECT = 2;
+
+/**
+ * Disabled option on forms and settings
+ */
+const DRUPAL_DISABLED = 0;
+
+/**
+ * Optional option on forms and settings
+ */
+const DRUPAL_OPTIONAL = 1;
+
+/**
+ * Required option on forms and settings
+ */
+const DRUPAL_REQUIRED = 2;
+
+/**
+ * Return only visible regions.
+ *
+ * @see system_region_list()
+ */
+const REGIONS_VISIBLE = 'visible';
+
+/**
+ * Return all regions.
+ *
+ * @see system_region_list()
+ */
+const REGIONS_ALL = 'all';
+
+/**
+ * Implements hook_help().
+ */
+function system_help($route_name, RouteMatchInterface $route_match) {
+  switch ($route_name) {
+    case 'help.page.system':
+      $output = '';
+      $output .= '<h3>' . t('About') . '</h3>';
+      $output .= '<p>' . t('The System module is integral to the site: it provides user interfaces for many core systems and settings, as well as the basic administrative menu structure. For more information, see the <a href="!system">online documentation for the System module</a>.', array('!system' => 'https://www.drupal.org/documentation/modules/system')) . '</p>';
+      $output .= '<h3>' . t('Uses') . '</h3>';
+      $output .= '<dl>';
+      $output .= '<dt>' . t('Managing modules') . '</dt>';
+      $output .= '<dd>' . t('Users with appropriate permission can install and uninstall modules from the <a href="!modules">Extend page</a>. Depending on which distribution or installation profile you choose when you install your site, several modules are installed and others are provided but not installed. Each module provides a discrete set of features; modules may be installed or uninstalled depending on the needs of the site. Many additional modules contributed by members of the Drupal community are available for download from the <a href="!drupal-modules">Drupal.org module page</a>. Note that uninstalling a module is a destructive action: when you uninstall a module, you will permanently lose all data connected to the module.', array('!modules' => \Drupal::url('system.modules_list'), '!drupal-modules' => 'https://www.drupal.org/project/modules')) . '</dd>';
+      $output .= '<dt>' . t('Managing themes') . '</dt>';
+      $output .= '<dd>' . t('Users with appropriate permission can install and uninstall themes on the <a href="!themes">Appearance page</a>. Themes determine the design and presentation of your site. Depending on which distribution or installation profile you choose when you install your site, a default theme is installed, and possibly a different theme for administration pages. Other themes are provided but not installed, and additional contributed themes are available at the <a href="!drupal-themes">Drupal.org theme page</a>.', array('!themes' => \Drupal::url('system.themes_page'), '!drupal-themes' => 'https://www.drupal.org/project/themes')) . '</dd>';
+      $output .= '<dt>' . t('Disabling drag-and-drop functionality') . '</dt>';
+      $output .= '<dd>' . t('The default drag-and-drop user interface for ordering tables in the administrative interface presents a challenge for some users, including users of screen readers and other assistive technology. The drag-and-drop interface can be disabled in a table by clicking a link labeled "Show row weights" above the table. The replacement interface allows users to order the table by choosing numerical weights instead of dragging table rows.') . '</dd>';
+      $output .= '<dt>' . t('Configuring basic site settings') . '</dt>';
+      $output .= '<dd>' . t('The System module provides pages for managing basic site configuration, including <a href="!date-time-settings">Date and time formats</a> and basic <a href="!site-info">Site information</a> (site name, email address to send mail from, home page, and error pages). Additional configuration pages are listed on the main <a href="!config">Configuration page</a>.', array('!date-time-settings' => \Drupal::url('entity.date_format.collection'), '!site-info' => \Drupal::url('system.site_information_settings'), '!config' => \Drupal::url('system.admin_config'))) . '</dd>';
+      $output .= '<dt>' . t('Using maintenance mode') . '</dt>';
+      $output .= '<dd>' . t('When you are performing site maintenance, you can prevent non-administrative users (including anonymous visitors) from viewing your site by putting it in <a href="!maintenance-mode">Maintenance mode</a>. This will prevent unauthorized users from making changes to the site while you are performing maintenance, or from seeing a broken site while updates are in progress.', array('!maintenance-mode' => \Drupal::url('system.site_maintenance_mode'))) . '</dd>';
+      $output .= '<dt>' . t('Configuring for performance') . '</dt>';
+      $output .= '<dd>' . t('On the <a href="!performance-page">Performance page</a>, the site can be configured to aggregate CSS and JavaScript files, making the total request size smaller. Note that, for small- to medium-sized websites, the <a href="!page-cache">Internal Page Cache module</a> should be installed so that pages are efficiently cached and reused.', array('!performance-page' => \Drupal::url('system.performance_settings'), '!page-cache' => (\Drupal::moduleHandler()->moduleExists('page_cache')) ? \Drupal::url('help.page', array('name' => 'page_cache')) : '#')) . '</dd>';
+      $output .= '<dt>' . t('Configuring cron') . '</dt>';
+      $output .= '<dd>' . t('In order for the site and its modules to continue to operate well, a set of routine administrative operations must run on a regular basis; these operations are known as <em>cron</em> tasks. On the <a href="!cron">Cron page</a>, you can configure cron to run periodically as part of normal page requests, or you can turn this off and trigger cron from an outside process on your web server. You can verify the status of cron tasks by visiting the <a href="!status">Status report page</a>. For more information, see the <a href="!handbook">online documentation for configuring cron jobs</a>.', array('!status' => \Drupal::url('system.status'), '!handbook' => 'https://www.drupal.org/cron', '!cron' => \Drupal::url('system.cron_settings'))) . '</dd>';
+      $output .= '<dt>' . t('Configuring the file system') . '</dt>';
+      $output .= '<dd>' . t('Your site has several file directories, which are used to store and process uploaded and generated files. The <em>public</em> file directory, which is configured in your settings.php file, is the default place for storing uploaded files. Links to files in this directory contain the direct file URL, so when the files are requested, the web server will send them directly without invoking your site code. This means that the files can be downloaded by anyone with the file URL, so requests are not access-controlled but they are efficient. The <em>private</em> file directory, also configured in your settings.php file and ideally located outside the site web root, is access controlled. Links to files in this directory are not direct, so requests to these files are mediated by your site code. This means that your site can check file access permission for each file before deciding to fulfill the request, so the requests are more secure, but less efficient. You should only use the private storage for files that need access control, not for files like your site logo and background images used on every page. The <em>temporary</em> file directory is used internally by your site code for various operations, and is configured on the <a href="!file-system">File system settings</a> page. You can also see the configured public and private file directories on this page, and choose whether public or private should be the default for uploaded files.', array('!file-system' => \Drupal::url('system.file_system_settings'))) . '</dd>';
+      $output .= '<dt>' . t('Configuring the image toolkit') . '</dt>';
+      $output .= '<dd>' . t('On the <a href="!toolkit">Image toolkit page</a>, you can select and configure the PHP toolkit used to manipulate images. Depending on which distribution or installation profile you choose when you install your site, the GD2 toolkit and possibly others are included; other toolkits may be provided by contributed modules.', array('!toolkit' => \Drupal::url('system.image_toolkit_settings'))) . '</dd>';
+      $output .= '</dl>';
+      return $output;
+
+    case 'system.admin_index':
+      return '<p>' . t('This page shows you all available administration tasks for each module.') . '</p>';
+
+    case 'system.themes_page':
+      $output = '<p>' . t('Set and configure the default theme for your website.  Alternative <a href="!themes">themes</a> are available.', array('!themes' => 'https://www.drupal.org/project/themes')) . '</p>';
+      if (\Drupal::moduleHandler()->moduleExists('block')) {
+        $output .= '<p>' . t('You can place blocks for each theme on the <a href="@blocks">block layout</a> page.', array('@blocks' => \Drupal::url('block.admin_display'))) . '</p>';
+      }
+      return $output;
+
+    case 'system.theme_settings_theme':
+      $theme_list = \Drupal::service('theme_handler')->listInfo();
+      $theme = $theme_list[$route_match->getParameter('theme')];
+      return '<p>' . t('These options control the display settings for the %name theme. When your site is displayed using this theme, these settings will be used.', array('%name' => $theme->info['name'])) . '</p>';
+
+    case 'system.theme_settings':
+      return '<p>' . t('These options control the default display settings for your entire site, across all themes. Unless they have been overridden by a specific theme, these settings will be used.') . '</p>';
+
+    case 'system.modules_list':
+      $output = '<p>' . t('Download additional <a href="!modules">contributed modules</a> to extend your site\'s functionality.', array('!modules' => 'https://www.drupal.org/project/modules')) . '</p>';
+      if (!\Drupal::moduleHandler()->moduleExists('update')) {
+        $output .= '<p>' . t('Regularly review available updates to maintain a secure and current site. Always run the <a href="!update-php">update script</a> each time a module is updated. Enable the <a href="!update-manager">Update Manager module</a> to update and install modules and themes.', array('!update-php' => \Drupal::url('system.db_update'), '!update-manager' => \Drupal::url('system.modules_list', [], ['fragment' => 'module-update']))) . '</p>';
+      }
+      return $output;
+
+    case 'system.modules_uninstall':
+      return '<p>' . t('The uninstall process removes all data related to a module.') . '</p>';
+
+    case 'entity.block.edit_form':
+      if (($block = $route_match->getParameter('block')) && $block->getPluginId() == 'system_powered_by_block') {
+        return '<p>' . t('The <em>Powered by Drupal</em> block is an optional link to the home page of the Drupal project. While there is absolutely no requirement that sites feature this link, it may be used to show support for Drupal.') . '</p>';
+      }
+      break;
+
+    case 'block.admin_add':
+      if ($route_match->getParameter('plugin_id') == 'system_powered_by_block') {
+        return '<p>' . t('The <em>Powered by Drupal</em> block is an optional link to the home page of the Drupal project. While there is absolutely no requirement that sites feature this link, it may be used to show support for Drupal.') . '</p>';
+      }
+      break;
+
+    case 'system.site_maintenance_mode':
+      if (\Drupal::currentUser()->id() == 1) {
+        return '<p>' . t('Use maintenance mode when making major updates, particularly if the updates could disrupt visitors or the update process. Examples include upgrading, importing or exporting content, modifying a theme, modifying content types, and making backups.') . '</p>';
+      }
+      break;
+
+    case 'system.status':
+      return '<p>' . t("Here you can find a short overview of your site's parameters as well as any problems detected with your installation. It may be useful to copy and paste this information into support requests filed on Drupal.org's support forums and project issue queues. Before filing a support request, ensure that your web server meets the <a href=\"!system-requirements\">system requirements.</a>", array('!system-requirements' => 'https://www.drupal.org/requirements')) . '</p>';
+  }
+}
+
+/**
+ * Implements hook_theme().
+ */
+function system_theme() {
+  return array_merge(drupal_common_theme(), array(
+    // Normally theme suggestion templates are only picked up when they are in
+    // themes. We explicitly define theme suggestions here so that the block
+    // templates in core/modules/system/templates are picked up.
+    'block__system_branding_block' => array(
+      'render element' => 'elements',
+      'base hook' => 'block',
+    ),
+    'block__system_messages_block' => array(
+      'base hook' => 'block',
+    ),
+    'block__system_menu_block' => array(
+      'render element' => 'elements',
+      'base hook' => 'block',
+    ),
+    'system_themes_page' => array(
+      'variables' => array(
+        'theme_groups' => array(),
+        'theme_group_titles' => array(),
+      ),
+      'file' => 'system.admin.inc',
+    ),
+    'system_config_form' => array(
+      'render element' => 'form',
+    ),
+    'confirm_form' => array(
+      'render element' => 'form',
+    ),
+    'system_modules_details' => array(
+      'render element' => 'form',
+      'file' => 'system.admin.inc',
+      'function' => 'theme_system_modules_details',
+    ),
+    'system_modules_uninstall' => array(
+      'render element' => 'form',
+      'file' => 'system.admin.inc',
+      'function' => 'theme_system_modules_uninstall',
+    ),
+    'status_report' => array(
+      'variables' => array('requirements' => NULL),
+      'file' => 'system.admin.inc',
+    ),
+    'admin_page' => array(
+      'variables' => array('blocks' => NULL),
+      'file' => 'system.admin.inc',
+    ),
+    'admin_block' => array(
+      'variables' => array('block' => NULL),
+      'file' => 'system.admin.inc',
+    ),
+    'admin_block_content' => array(
+      'variables' => array('content' => NULL),
+      'file' => 'system.admin.inc',
+    ),
+    'system_admin_index' => array(
+      'variables' => array('menu_items' => NULL),
+      'file' => 'system.admin.inc',
+    ),
+  ));
+}
+
+/**
+ * Implements hook_hook_info().
+ */
+function system_hook_info() {
+  $hooks['token_info'] = array(
+    'group' => 'tokens',
+  );
+  $hooks['token_info_alter'] = array(
+    'group' => 'tokens',
+  );
+  $hooks['tokens'] = array(
+    'group' => 'tokens',
+  );
+  $hooks['tokens_alter'] = array(
+    'group' => 'tokens',
+  );
+
+  return $hooks;
+}
+
+/**
+ * Implements hook_theme_suggestions_HOOK().
+ */
+function system_theme_suggestions_html(array $variables) {
+  if (\Drupal::service('path.matcher')->isFrontPage()) {
+    $path_args = [''];
+  }
+  else {
+    $path_args = explode('/', ltrim(\Drupal::service('path.current')->getPath(), '/'));
+  }
+  return theme_get_suggestions($path_args, 'html');
+}
+
+/**
+ * Implements hook_theme_suggestions_HOOK().
+ */
+function system_theme_suggestions_page(array $variables) {
+  if (\Drupal::service('path.matcher')->isFrontPage()) {
+    $path_args = [''];
+  }
+  else {
+    $path_args = explode('/', Url::fromRoute('<current>')->getInternalPath());
+  }
+  return theme_get_suggestions($path_args, 'page');
+}
+
+/**
+ * Implements hook_theme_suggestions_HOOK().
+ */
+function system_theme_suggestions_maintenance_page(array $variables) {
+  $suggestions = array();
+
+  // Dead databases will show error messages so supplying this template will
+  // allow themers to override the page and the content completely.
+  $offline = defined('MAINTENANCE_MODE');
+  try {
+    \Drupal::service('path.matcher')->isFrontPage();
+  }
+  catch (Exception $e) {
+    // The database is not yet available.
+    $offline = TRUE;
+  }
+  if ($offline) {
+    $suggestions[] = 'maintenance_page__offline';
+  }
+
+  return $suggestions;
+}
+
+/**
+ * Implements hook_theme_suggestions_HOOK().
+ */
+function system_theme_suggestions_region(array $variables) {
+  $suggestions = array();
+  if (!empty($variables['elements']['#region'])) {
+    $suggestions[] = 'region__' . $variables['elements']['#region'];
+  }
+  return $suggestions;
+}
+
+/**
+ * Implements hook_theme_suggestions_HOOK().
+ */
+function system_theme_suggestions_field(array $variables) {
+  $suggestions = array();
+  $element = $variables['element'];
+
+  $suggestions[] = 'field__' . $element['#field_type'];
+  $suggestions[] = 'field__' . $element['#field_name'];
+  $suggestions[] = 'field__' . $element['#entity_type'] . '__' . $element['#bundle'];
+  $suggestions[] = 'field__' . $element['#entity_type'] . '__' . $element['#field_name'];
+  $suggestions[] = 'field__' . $element['#entity_type'] . '__' . $element['#field_name'] . '__' . $element['#bundle'];
+
+  return $suggestions;
+}
+
+/**
+ * @defgroup authorize Authorized operations
+ * @{
+ * Functions to run operations with elevated privileges via authorize.php.
+ *
+ * Because of the Update manager functionality included in Drupal core, there
+ * is a mechanism for running operations with elevated file system privileges,
+ * the top-level authorize.php script. This script runs at a reduced Drupal
+ * bootstrap level so that it is not reliant on the entire site being
+ * functional. The operations use a FileTransfer class to manipulate code
+ * installed on the system as the user that owns the files, not the user that
+ * the httpd is running as.
+ *
+ * The first setup is to define a callback function that should be authorized
+ * to run with the elevated privileges. This callback should take a
+ * FileTransfer as its first argument, although you can define an array of
+ * other arguments it should be invoked with. The callback should be placed in
+ * a separate .inc file that will be included by authorize.php.
+ *
+ * To run the operation, certain data must be saved into the SESSION, and then
+ * the flow of control should be redirected to the authorize.php script. There
+ * are two ways to do this, either to call system_authorized_run() directly,
+ * or to call system_authorized_init() and then redirect to authorize.php,
+ * using the URL from system_authorized_get_url(). Redirecting yourself is
+ * necessary when your authorized operation is being triggered by a form
+ * submit handler, since calling redirecting in a submit handler is a bad
+ * idea, and you should instead use $form_state->setRedirect().
+ *
+ * Once the SESSION is setup for the operation and the user is redirected to
+ * authorize.php, they will be prompted for their connection credentials (core
+ * provides FTP and SSH by default, although other connection classes can be
+ * added via contributed modules). With valid credentials, authorize.php will
+ * instantiate the appropriate FileTransfer object, and then invoke the
+ * desired operation passing in that object. The authorize.php script can act
+ * as a Batch API processing page, if the operation requires a batch.
+ *
+ * @see authorize.php
+ * @see \Drupal\Core\FileTransfer\FileTransfer
+ * @see hook_filetransfer_info()
+ */
+
+/**
+ * Setup a given callback to run via authorize.php with elevated privileges.
+ *
+ * To use authorize.php, certain variables must be stashed into $_SESSION. This
+ * function sets up all the necessary $_SESSION variables. The calling function
+ * should then redirect to authorize.php, using the full path returned by
+ * system_authorized_get_url(). That initiates the workflow that will eventually
+ * lead to the callback being invoked. The callback will be invoked at a low
+ * bootstrap level, without all modules being invoked, so it needs to be careful
+ * not to assume any code exists. Example (system_authorized_run()):
+ * @code
+ *   system_authorized_init($callback, $file, $arguments, $page_title);
+ *   return new RedirectResponse(system_authorized_get_url()->toString());
+ * @endcode
+ * Example (update_manager_install_form_submit()):
+ * @code
+ *  system_authorized_init('update_authorize_run_install',
+ *    drupal_get_path('module', 'update') . '/update.authorize.inc',
+ *    $arguments, t('Update manager'));
+ *  $form_state->setRedirectUrl(system_authorized_get_url());
+ * @endcode
+ *
+ * @param $callback
+ *   The name of the function to invoke once the user authorizes the operation.
+ * @param $file
+ *   The full path to the file where the callback function is implemented.
+ * @param $arguments
+ *   Optional array of arguments to pass into the callback when it is invoked.
+ *   Note that the first argument to the callback is always the FileTransfer
+ *   object created by authorize.php when the user authorizes the operation.
+ * @param $page_title
+ *   Optional string to use as the page title once redirected to authorize.php.
+ * @return
+ *   Nothing, this function just initializes variables in the user's session.
+ */
+function system_authorized_init($callback, $file, $arguments = array(), $page_title = NULL) {
+  // First, figure out what file transfer backends the site supports, and put
+  // all of those in the SESSION so that authorize.php has access to all of
+  // them via the class autoloader, even without a full bootstrap.
+  $_SESSION['authorize_filetransfer_info'] = drupal_get_filetransfer_info();
+
+  // Now, define the callback to invoke.
+  $_SESSION['authorize_operation'] = array(
+    'callback' => $callback,
+    'file' => $file,
+    'arguments' => $arguments,
+  );
+
+  if (isset($page_title)) {
+    $_SESSION['authorize_page_title'] = $page_title;
+  }
+}
+
+/**
+ * Return the URL for the authorize.php script.
+ *
+ * @param array $options
+ *   Optional array of options to pass to url().
+ * @return \Drupal\Core\Url
+ *   The full URL to authorize.php, using HTTPS if available.
+ *
+ * @see system_authorized_init()
+ */
+function system_authorized_get_url(array $options = array()) {
+  // core/authorize.php is an unrouted URL, so using the base: scheme is
+  // the correct usage for this case.
+  $url = Url::fromUri('base:core/authorize.php');
+  $url_options = $url->getOptions();
+  $url->setOptions($options + $url_options);
+  return $url;
+}
+
+/**
+ * Returns the URL for the authorize.php script when it is processing a batch.
+ *
+ * @param array $options
+ *   Optional array of options to pass to url().
+ *
+ * @return \Drupal\Core\Url
+ */
+function system_authorized_batch_processing_url(array $options = array()) {
+  $options['query'] = array('batch' => '1');
+  return system_authorized_get_url($options);
+}
+
+/**
+ * Setup and invoke an operation using authorize.php.
+ *
+ * @see system_authorized_init()
+ */
+function system_authorized_run($callback, $file, $arguments = array(), $page_title = NULL) {
+  system_authorized_init($callback, $file, $arguments, $page_title);
+  return new RedirectResponse(system_authorized_get_url()->toString());
+}
+
+/**
+ * Use authorize.php to run batch_process().
+ *
+ * @see batch_process()
+ */
+function system_authorized_batch_process() {
+  $finish_url = system_authorized_get_url();
+  $process_url = system_authorized_batch_processing_url();
+  return batch_process($finish_url->setAbsolute()->toString(), $process_url);
+}
+
+/**
+ * @} End of "defgroup authorize".
+ */
+
+/**
+ * Implements hook_updater_info().
+ */
+function system_updater_info() {
+  return array(
+    'module' => array(
+      'class' => 'Drupal\Core\Updater\Module',
+      'name' => t('Update modules'),
+      'weight' => 0,
+    ),
+    'theme' => array(
+      'class' => 'Drupal\Core\Updater\Theme',
+      'name' => t('Update themes'),
+      'weight' => 0,
+    ),
+  );
+}
+
+/**
+ * Implements hook_filetransfer_info().
+ */
+function system_filetransfer_info() {
+  $backends = array();
+
+  // This is the default, will be available on most systems.
+  if (function_exists('ftp_connect')) {
+    $backends['ftp'] = array(
+      'title' => t('FTP'),
+      'class' => 'Drupal\Core\FileTransfer\FTP',
+      'weight' => 0,
+    );
+  }
+
+  // SSH2 lib connection is only available if the proper PHP extension is
+  // installed.
+  if (function_exists('ssh2_connect')) {
+    $backends['ssh'] = array(
+      'title' => t('SSH'),
+      'class' => 'Drupal\Core\FileTransfer\SSH',
+      'weight' => 20,
+    );
+  }
+  return $backends;
+}
+
+/**
+ * Implements hook_page_attachments().
+ *
+ * @see template_preprocess_maintenance_page()
+ * @see \Drupal\Core\EventSubscriber\ActiveLinkResponseFilter
+ */
+function system_page_attachments(array &$page) {
+  // Ensure the same CSS is loaded in template_preprocess_maintenance_page().
+  $page['#attached']['library'][] = 'system/base';
+  if (\Drupal::service('router.admin_context')->isAdminRoute()) {
+    $page['#attached']['library'][] = 'system/admin';
+  }
+
+  // Attach libraries used by this theme.
+  $active_theme = \Drupal::theme()->getActiveTheme();
+  foreach ($active_theme->getLibraries() as $library) {
+    $page['#attached']['library'][] = $library;
+  }
+
+  // Attach favicon.
+  if (theme_get_setting('features.favicon')) {
+    $favicon = theme_get_setting('favicon.url');
+    $type = theme_get_setting('favicon.mimetype');
+    $page['#attached']['html_head_link'][][] = array(
+      'rel' => 'shortcut icon',
+      'href' => UrlHelper::stripDangerousProtocols($favicon),
+      'type' => $type,
+    );
+  }
+
+  // Get the major Drupal version.
+  list($version, ) = explode('.', \Drupal::VERSION);
+
+  // Attach default meta tags.
+  $meta_default = array(
+    // Make sure the Content-Type comes first because the IE browser may be
+    // vulnerable to XSS via encoding attacks from any content that comes
+    // before this META tag, such as a TITLE tag.
+    'system_meta_content_type' => array(
+      '#tag' => 'meta',
+      '#attributes' => array(
+        'charset' => 'utf-8',
+      ),
+      // Security: This always has to be output first.
+      '#weight' => -1000,
+    ),
+    // Show Drupal and the major version number in the META GENERATOR tag.
+    'system_meta_generator' => array(
+      '#type' => 'html_tag',
+      '#tag' => 'meta',
+      '#attributes' => array(
+        'name' => 'Generator',
+        'content' => 'Drupal ' . $version . ' (https://www.drupal.org)',
+      ),
+    ),
+    // Attach default mobile meta tags for responsive design.
+    'MobileOptimized' => array(
+      '#tag' => 'meta',
+      '#attributes' => array(
+        'name' => 'MobileOptimized',
+        'content' => 'width',
+      ),
+    ),
+    'HandheldFriendly' => array(
+      '#tag' => 'meta',
+      '#attributes' => array(
+        'name' => 'HandheldFriendly',
+        'content' => 'true',
+      ),
+    ),
+    'viewport' => array(
+      '#tag' => 'meta',
+      '#attributes' => array(
+        'name' => 'viewport',
+        'content' => 'width=device-width, initial-scale=1.0',
+      ),
+    ),
+  );
+  foreach ($meta_default as $key => $value) {
+    $page['#attached']['html_head'][] = [$value, $key];
+  }
+
+  // Handle setting the "active" class on links by:
+  // - loading the active-link library if the current user is authenticated;
+  // - applying a response filter if the current user is anonymous.
+  // @see l()
+  // @see \Drupal\Core\Utility\LinkGenerator::generate()
+  // @see template_preprocess_links()
+  // @see \Drupal\Core\EventSubscriber\ActiveLinkResponseFilter
+  if (\Drupal::currentUser()->isAuthenticated()) {
+    $page['#attached']['library'][] = 'core/drupal.active-link';
+  }
+}
+
+/**
+ * Implements hook_js_settings_build().
+ *
+ * Sets values for the core/drupal.ajax library, which just depends on the
+ * active theme but no other request-dependent values.
+ */
+function system_js_settings_build(&$settings, AttachedAssetsInterface $assets) {
+  // Generate the values for the core/drupal.ajax library.
+  // We need to send ajaxPageState settings for core/drupal.ajax if:
+  // - ajaxPageState is being loaded in this Response, in which case it will
+  //   already exist at $settings['ajaxPageState'] (because the core/drupal.ajax
+  //   library definition specifies a placeholder 'ajaxPageState' setting).
+  // - core/drupal.ajax already has been loaded and hence this is an AJAX
+  //   Response in which we must send the list of extra asset libraries that are
+  //   being added in this AJAX Response.
+  /** @var \Drupal\Core\Asset\LibraryDependencyResolver $library_dependency_resolver */
+  $library_dependency_resolver = \Drupal::service('library.dependency_resolver');
+  if (isset($settings['ajaxPageState']) || in_array('core/drupal.ajax', $library_dependency_resolver->getLibrariesWithDependencies($assets->getAlreadyLoadedLibraries()))) {
+    // Provide the page with information about the theme that's used, so that
+    // a later AJAX request can be rendered using the same theme.
+    // @see \Drupal\Core\Theme\AjaxBasePageNegotiator
+    $theme_key = \Drupal::theme()->getActiveTheme()->getName();
+    $settings['ajaxPageState']['theme'] = $theme_key;
+
+    // Provide the page with information about the individual asset libraries
+    // used, information not otherwise available when aggregation is enabled.
+    $minimal_libraries = $library_dependency_resolver->getMinimalRepresentativeSubset(array_merge(
+      $assets->getLibraries(),
+      $assets->getAlreadyLoadedLibraries()
+    ));
+    sort($minimal_libraries);
+    $settings['ajaxPageState']['libraries'] = implode(',', $minimal_libraries);
+  }
+}
+
+/**
+ * Implements hook_js_settings_alter().
+ *
+ * Sets values which depend on the current request, like core/drupalSettings
+ * as well as theme_token ajax state.
+ */
+function system_js_settings_alter(&$settings, AttachedAssetsInterface $assets) {
+  $request = \Drupal::request();
+  $current_query = $request->query->all();
+
+  // Let output path processors set a prefix.
+  /** @var \Drupal\Core\PathProcessor\OutboundPathProcessorInterface $path_processor */
+  $path_processor = \Drupal::service('path_processor_manager');
+  $options = ['prefix' => ''];
+  $path_processor->processOutbound('/', $options);
+  $pathPrefix = $options['prefix'];
+
+  $current_path = \Drupal::routeMatch()->getRouteName() ? Url::fromRouteMatch(\Drupal::routeMatch())->getInternalPath() : '';
+  $current_path_is_admin = \Drupal::service('router.admin_context')->isAdminRoute();
+  $path_settings = [
+    'baseUrl' => $request->getBaseUrl() . '/',
+    'pathPrefix' => $pathPrefix,
+    'currentPath' => $current_path,
+    'currentPathIsAdmin' => $current_path_is_admin,
+    'isFront' => \Drupal::service('path.matcher')->isFrontPage(),
+    'currentLanguage' => \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_URL)->getId(),
+  ];
+  if (!empty($current_query)) {
+    ksort($current_query);
+    $path_settings['currentQuery'] = (object) $current_query;
+  }
+
+  // Only set core/drupalSettings values that haven't been set already.
+  foreach ($path_settings as $key => $value) {
+    if (!isset($settings['path'][$key])) {
+      $settings['path'][$key] = $value;
+    }
+  }
+  if (!isset($settings['pluralDelimiter'])) {
+    $settings['pluralDelimiter'] = LOCALE_PLURAL_DELIMITER;
+  }
+  // Add the theme token to ajaxPageState, ensuring the database is available
+  // before doing so.
+  /** @var \Drupal\Core\Asset\LibraryDependencyResolver $library_dependency_resolver */
+  $library_dependency_resolver = \Drupal::service('library.dependency_resolver');
+  if (isset($settings['ajaxPageState']) || in_array('core/drupal.ajax', $library_dependency_resolver->getLibrariesWithDependencies($assets->getAlreadyLoadedLibraries()))) {
+    if (!defined('MAINTENANCE_MODE')) {
+      // The theme token is only validated when the theme requested is not the
+      // default, so don't generate it unless necessary.
+      // @see \Drupal\Core\Theme\AjaxBasePageNegotiator::determineActiveTheme()
+      $active_theme_key = \Drupal::theme()->getActiveTheme()->getName();
+      if ($active_theme_key !== \Drupal::service('theme_handler')->getDefault()) {
+        $settings['ajaxPageState']['theme_token'] = \Drupal::csrfToken()
+          ->get($active_theme_key);
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_form_alter().
+ */
+function system_form_alter(&$form, FormStateInterface $form_state) {
+  // If the page that's being built is cacheable, set the 'immutable' flag, to
+  // ensure that when the form is used, a new form build ID is generated when
+  // appropriate, to prevent information disclosure.
+
+  // Note: This code just wants to know whether cache response headers are set,
+  // not whether page_cache module will be active.
+  // \Drupal\Core\EventSubscriber\FinishResponseSubscriber::onRespond will
+  // send those headers, in case $request_policy->check($request) succeeds. In
+  // that case we need to ensure that the immutable flag is sot, so future POST
+  // request won't take over the form state of another user.
+  /** @var \Drupal\Core\PageCache\RequestPolicyInterface $request_policy */
+  $request_policy = \Drupal::service('page_cache_request_policy');
+  $request = \Drupal::requestStack()->getCurrentRequest();
+  $request_is_cacheable = $request_policy->check($request) === RequestPolicyInterface::ALLOW;
+  if ($request_is_cacheable) {
+    $form_state->addBuildInfo('immutable', TRUE);
+  }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function system_form_user_form_alter(&$form, FormStateInterface $form_state) {
+  if (\Drupal::config('system.date')->get('timezone.user.configurable')) {
+    system_user_timezone($form, $form_state);
+  }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function system_form_user_register_form_alter(&$form, FormStateInterface $form_state) {
+  $config = \Drupal::config('system.date');
+  if ($config->get('timezone.user.configurable') && $config->get('timezone.user.default') == DRUPAL_USER_TIMEZONE_SELECT) {
+    system_user_timezone($form, $form_state);
+  }
+}
+
+/**
+ * Implements hook_ENTITY_TYPE_presave() for user entities.
+ */
+function system_user_presave(UserInterface $account) {
+  $config = \Drupal::config('system.date');
+  if ($config->get('timezone.user.configurable') && !$account->getTimeZone() && !$config->get('timezone.user.default')) {
+    $account->timezone = $config->get('timezone.default');
+  }
+}
+
+/**
+ * Implements hook_user_login().
+ */
+function system_user_login(UserInterface $account) {
+  $config = \Drupal::config('system.date');
+  // If the user has a NULL time zone, notify them to set a time zone.
+  if (!$account->getTimezone() && $config->get('timezone.user.configurable') && $config->get('timezone.user.warn')) {
+    drupal_set_message(t('Configure your <a href="@user-edit">account time zone setting</a>.', array('@user-edit' => $account->url('edit-form', array('query' => \Drupal::destination()->getAsArray(), 'fragment' => 'edit-timezone')))));
+  }
+}
+
+/**
+ * Add the time zone field to the user edit and register forms.
+ */
+function system_user_timezone(&$form, FormStateInterface $form_state) {
+  $user = \Drupal::currentUser();
+
+  $account = $form_state->getFormObject()->getEntity();
+  $form['timezone'] = array(
+    '#type' => 'details',
+    '#title' => t('Locale settings'),
+    '#open' => TRUE,
+    '#weight' => 6,
+  );
+  $form['timezone']['timezone'] = array(
+    '#type' => 'select',
+    '#title' => t('Time zone'),
+    '#default_value' => $account->getTimezone() ? $account->getTimezone() : \Drupal::config('system.date')->get('timezone.default'),
+    '#options' => system_time_zones($account->id() != $user->id()),
+    '#description' => t('Select the desired local time and time zone. Dates and times throughout this site will be displayed using this time zone.'),
+  );
+  $user_input = $form_state->getUserInput();
+  if (!$account->getTimezone() && $account->id() == $user->id() && empty($user_input['timezone'])) {
+    $form['timezone']['#attached']['library'][] = 'core/drupal.timezone';
+    $form['timezone']['timezone']['#attributes'] = array('class' => array('timezone-detect'));
+  }
+}
+
+/**
+ * Implements hook_preprocess_HOOK() for block templates.
+ */
+function system_preprocess_block(&$variables) {
+  switch ($variables['base_plugin_id']) {
+    case 'system_branding_block':
+      $variables['site_logo'] = '';
+      if ($variables['content']['site_logo']['#access'] && $variables['content']['site_logo']['#uri']) {
+        $variables['site_logo'] = $variables['content']['site_logo']['#uri'];
+      }
+      $variables['site_name'] = '';
+      if ($variables['content']['site_name']['#access'] && $variables['content']['site_name']['#markup']) {
+        $variables['site_name'] = $variables['content']['site_name']['#markup'];
+      }
+      $variables['site_slogan'] = '';
+      if ($variables['content']['site_slogan']['#access'] && $variables['content']['site_slogan']['#markup']) {
+        $variables['site_slogan']['#markup'] = $variables['content']['site_slogan']['#markup'];
+      }
+      break;
+
+    case 'system_powered_by_block':
+      $variables['attributes']['role'] = 'complementary';
+      break;
+  }
+}
+
+/**
+ * Checks the existence of the directory specified in $form_element.
+ *
+ * This function is called from the system_settings form to check all core
+ * file directories (file_public_path, file_private_path, file_temporary_path).
+ *
+ * @param $form_element
+ *   The form element containing the name of the directory to check.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ *   The current state of the form.
+ */
+function system_check_directory($form_element, FormStateInterface $form_state) {
+  $directory = $form_element['#value'];
+  if (strlen($directory) == 0) {
+    return $form_element;
+  }
+
+  $logger = \Drupal::logger('file system');
+  if (!is_dir($directory) && !drupal_mkdir($directory, NULL, TRUE)) {
+    // If the directory does not exists and cannot be created.
+    $form_state->setErrorByName($form_element['#parents'][0], t('The directory %directory does not exist and could not be created.', array('%directory' => $directory)));
+    $logger->error('The directory %directory does not exist and could not be created.', array('%directory' => $directory));
+  }
+
+  if (is_dir($directory) && !is_writable($directory) && !drupal_chmod($directory)) {
+    // If the directory is not writable and cannot be made so.
+    $form_state->setErrorByName($form_element['#parents'][0], t('The directory %directory exists but is not writable and could not be made writable.', array('%directory' => $directory)));
+    $logger->error('The directory %directory exists but is not writable and could not be made writable.', array('%directory' => $directory));
+  }
+  elseif (is_dir($directory)) {
+    if ($form_element['#name'] == 'file_public_path') {
+      // Create public .htaccess file.
+      file_save_htaccess($directory, FALSE);
+    }
+    else {
+      // Create private .htaccess file.
+      file_save_htaccess($directory);
+    }
+  }
+
+  return $form_element;
+}
+
+/**
+ * Returns an array of information about enabled modules or themes.
+ *
+ * This function returns the contents of the .info.yml file for each installed
+ * module or theme.
+ *
+ * @param $type
+ *   Either 'module' or 'theme'.
+ * @param $name
+ *   (optional) The name of a module or theme whose information shall be
+ *   returned. If omitted, all records for the provided $type will be returned.
+ *   If $name does not exist in the provided $type or is not enabled, an empty
+ *   array will be returned.
+ *
+ * @return
+ *   An associative array of module or theme information keyed by name, or only
+ *   information for $name, if given. If no records are available, an empty
+ *   array is returned.
+ *
+ * @see system_rebuild_module_data()
+ * @see \Drupal\Core\Extension\ThemeHandlerInterface::rebuildThemeData()
+ */
+function system_get_info($type, $name = NULL) {
+  if ($type == 'module') {
+    $info = &drupal_static(__FUNCTION__);
+    if (!isset($info)) {
+      if ($cache = \Drupal::cache()->get('system.module.info')) {
+        $info = $cache->data;
+      }
+      else {
+        $data = system_rebuild_module_data();
+        foreach (\Drupal::moduleHandler()->getModuleList() as $module => $filename) {
+          if (isset($data[$module])) {
+            $info[$module] = $data[$module]->info;
+          }
+        }
+        // Store the module information in cache. This cache is cleared by
+        // calling system_rebuild_module_data(), for example, when listing
+        // modules, (un)installing modules, importing configuration, updating
+        // the site and when flushing all the caches.
+        \Drupal::cache()->set('system.module.info', $info);
+      }
+    }
+  }
+  else {
+    $info = array();
+    $list = system_list($type);
+    foreach ($list as $shortname => $item) {
+      if (!empty($item->status)) {
+        $info[$shortname] = $item->info;
+      }
+    }
+  }
+  if (isset($name)) {
+    return isset($info[$name]) ? $info[$name] : array();
+  }
+  return $info;
+}
+
+/**
+ * Helper function to scan and collect module .info.yml data.
+ *
+ * @return \Drupal\Core\Extension\Extension[]
+ *   An associative array of module information.
+ */
+function _system_rebuild_module_data() {
+  $listing = new ExtensionDiscovery(\Drupal::root());
+
+  // Find installation profiles. This needs to happen before performing a
+  // module scan as the module scan requires knowing what the active profile is.
+  // @todo Remove as part of https://www.drupal.org/node/2186491.
+  $profiles = $listing->scan('profile');
+  $profile = drupal_get_profile();
+  if ($profile && isset($profiles[$profile])) {
+    // Prime the drupal_get_filename() static cache with the profile info file
+    // location so we can use drupal_get_path() on the active profile during
+    // the module scan.
+    // @todo Remove as part of https://www.drupal.org/node/2186491.
+    drupal_get_filename('profile', $profile, $profiles[$profile]->getPathname());
+  }
+
+  // Find modules.
+  $modules = $listing->scan('module');
+  // Include the installation profile in modules that are loaded.
+  if ($profile) {
+    $modules[$profile] = $profiles[$profile];
+    // Installation profile hooks are always executed last.
+    $modules[$profile]->weight = 1000;
+  }
+
+  // Set defaults for module info.
+  $defaults = array(
+    'dependencies' => array(),
+    'description' => '',
+    'package' => 'Other',
+    'version' => NULL,
+    'php' => DRUPAL_MINIMUM_PHP,
+  );
+
+  // Read info files for each module.
+  foreach ($modules as $key => $module) {
+    // Look for the info file.
+    $module->info = \Drupal::service('info_parser')->parse($module->getPathname());
+
+    // Add the info file modification time, so it becomes available for
+    // contributed modules to use for ordering module lists.
+    $module->info['mtime'] = $module->getMTime();
+
+    // Merge in defaults and save.
+    $modules[$key]->info = $module->info + $defaults;
+
+    // Installation profiles are hidden by default, unless explicitly specified
+    // otherwise in the .info.yml file.
+    if ($key == $profile && !isset($modules[$key]->info['hidden'])) {
+      $modules[$key]->info['hidden'] = TRUE;
+    }
+
+    // Invoke hook_system_info_alter() to give installed modules a chance to
+    // modify the data in the .info.yml files if necessary.
+    // @todo Remove $type argument, obsolete with $module->getType().
+    $type = 'module';
+    \Drupal::moduleHandler()->alter('system_info', $modules[$key]->info, $modules[$key], $type);
+  }
+
+  // It is possible that a module was marked as required by
+  // hook_system_info_alter() and modules that it depends on are not required.
+  foreach ($modules as $module) {
+    _system_rebuild_module_data_ensure_required($module, $modules);
+  }
+
+
+  if ($profile && isset($modules[$profile])) {
+    // The installation profile is required, if it's a valid module.
+    $modules[$profile]->info['required'] = TRUE;
+    // Add a default distribution name if the profile did not provide one.
+    // @see install_profile_info()
+    // @see drupal_install_profile_distribution_name()
+    if (!isset($modules[$profile]->info['distribution']['name'])) {
+      $modules[$profile]->info['distribution']['name'] = 'Drupal';
+    }
+  }
+
+  return $modules;
+}
+
+/**
+ * Ensures that dependencies of required modules are also required.
+ *
+ * @param \Drupal\Core\Extension\Extension $module
+ *   The module info.
+ * @param \Drupal\Core\Extension\Extension[] $modules
+ *   The array of all module info.
+ */
+function _system_rebuild_module_data_ensure_required($module, &$modules) {
+  if (!empty($module->info['required'])) {
+    foreach ($module->info['dependencies'] as $dependency) {
+      $dependency_name = ModuleHandler::parseDependency($dependency)['name'];
+      if (!isset($modules[$dependency_name]->info['required'])) {
+        $modules[$dependency_name]->info['required'] = TRUE;
+        $modules[$dependency_name]->info['explanation'] = t('Dependency of required module @module', array('@module' => $module->info['name']));
+        // Ensure any dependencies it has are required.
+        _system_rebuild_module_data_ensure_required($modules[$dependency_name], $modules);
+      }
+    }
+  }
+}
+
+/**
+ * Rebuild, save, and return data about all currently available modules.
+ *
+ * @return \Drupal\Core\Extension\Extension[]
+ *   Array of all available modules and their data.
+ */
+function system_rebuild_module_data() {
+  $modules_cache = &drupal_static(__FUNCTION__);
+  // Only rebuild once per request. $modules and $modules_cache cannot be
+  // combined into one variable, because the $modules_cache variable is reset by
+  // reference from system_list_reset() during the rebuild.
+  if (!isset($modules_cache)) {
+    $modules = _system_rebuild_module_data();
+    $files = array();
+    ksort($modules);
+    // Add status, weight, and schema version.
+    $installed_modules = \Drupal::config('core.extension')->get('module') ?: array();
+    foreach ($modules as $name => $module) {
+      $module->weight = isset($installed_modules[$name]) ? $installed_modules[$name] : 0;
+      $module->status = (int) isset($installed_modules[$name]);
+      $module->schema_version = SCHEMA_UNINSTALLED;
+      $files[$name] = $module->getPathname();
+    }
+    $modules = \Drupal::moduleHandler()->buildModuleDependencies($modules);
+    $modules_cache = $modules;
+
+    // Store filenames to allow drupal_get_filename() to retrieve them without
+    // having to rebuild or scan the filesystem.
+    \Drupal::state()->set('system.module.files', $files);
+    // Clear the module info cache.
+    \Drupal::cache()->delete('system.module.info');
+    drupal_static_reset('system_get_info');
+  }
+  return $modules_cache;
+}
+
+/**
+ * Returns an array of default theme features.
+ */
+function _system_default_theme_features() {
+  return array(
+    'logo',
+    'favicon',
+    'name',
+    'slogan',
+    'node_user_picture',
+    'comment_user_picture',
+    'comment_user_verification',
+  );
+}
+
+/**
+ * Get a list of available regions from a specified theme.
+ *
+ * @param \Drupal\Core\Extension\Extension|string $theme
+ *   A theme extension object, or the name of a theme.
+ * @param $show
+ *   Possible values: REGIONS_ALL or REGIONS_VISIBLE. Visible excludes hidden
+ *   regions.
+ * @return
+ *   An array of regions in the form $region['name'] = 'description'.
+ */
+function system_region_list($theme, $show = REGIONS_ALL) {
+  if (!$theme instanceof Extension) {
+    $themes = \Drupal::service('theme_handler')->listInfo();
+    if (!isset($themes[$theme])) {
+      return array();
+    }
+    $theme = $themes[$theme];
+  }
+  $list = array();
+  $info = $theme->info;
+  // If requested, suppress hidden regions. See block_admin_display_form().
+  foreach ($info['regions'] as $name => $label) {
+    if ($show == REGIONS_ALL || !isset($info['regions_hidden']) || !in_array($name, $info['regions_hidden'])) {
+      $list[$name] = t($label);
+    }
+  }
+
+  return $list;
+}
+
+/**
+ * Array sorting callback; sorts modules by their name.
+ */
+function system_sort_modules_by_info_name($a, $b) {
+  return strcasecmp($a->info['name'], $b->info['name']);
+}
+
+/**
+ * Sorts themes by their names, with the default theme listed first.
+ *
+ * Callback for uasort() within
+ * \Drupal\system\Controller\SystemController::themesPage().
+ *
+ * @see system_sort_modules_by_info_name().
+ */
+function system_sort_themes($a, $b) {
+  if ($a->is_default) {
+    return -1;
+  }
+  if ($b->is_default) {
+    return 1;
+  }
+  return strcasecmp($a->info['name'], $b->info['name']);
+}
+
+/**
+ * Implements hook_system_info_alter().
+ */
+function system_system_info_alter(&$info, Extension $file, $type) {
+  // Remove page-top and page-bottom from the blocks UI since they are reserved for
+  // modules to populate from outside the blocks system.
+  if ($type == 'theme') {
+    $info['regions_hidden'][] = 'page_top';
+    $info['regions_hidden'][] = 'page_bottom';
+  }
+}
+
+/**
+ * Gets the name of the default region for a given theme.
+ *
+ * @param $theme
+ *   The name of a theme.
+ * @return
+ *   A string that is the region name.
+ */
+function system_default_region($theme) {
+  $regions = array_keys(system_region_list($theme, REGIONS_VISIBLE));
+  return isset($regions[0]) ? $regions[0] : '';
+}
+
+/**
+ * Determines whether the current user is in compact mode.
+ *
+ * Compact mode shows certain administration pages with less description text,
+ * such as the configuration page and the permissions page.
+ *
+ * Whether the user is in compact mode is determined by a cookie, which is set
+ * for the user by \Drupal\system\Controller\SystemController::compactPage().
+ *
+ * If the user does not have the cookie, the default value is given by the
+ * system variable 'admin_compact_mode', which itself defaults to FALSE. This
+ * does not have a user interface to set it: it is a hidden variable which can
+ * be set in the settings.php file.
+ *
+ * @return bool
+ *   TRUE when in compact mode, FALSE when in expanded mode.
+ */
+function system_admin_compact_mode() {
+  // PHP converts dots into underscores in cookie names to avoid problems with
+  // its parser, so we use a converted cookie name.
+  return \Drupal::request()->cookies->get('Drupal_visitor_admin_compact_mode', \Drupal::config('system.site')->get('admin_compact_mode'));
+}
+
+/**
+ * Generate a list of tasks offered by a specified module.
+ *
+ * @param string $module
+ *   Module name.
+ * @param array $info
+ *   The module's information, as provided by system_get_info().
+ *
+ * @return array
+ *   An array of task links.
+ */
+function system_get_module_admin_tasks($module, array $info) {
+  $tree = &drupal_static(__FUNCTION__);
+
+  $menu_tree = \Drupal::menuTree();
+
+  if (!isset($tree)) {
+    $parameters = new MenuTreeParameters();
+    $parameters->setRoot('system.admin')->excludeRoot()->onlyEnabledLinks();
+    $tree = $menu_tree->load('system.admin', $parameters);
+    $manipulators = array(
+      array('callable' => 'menu.default_tree_manipulators:checkAccess'),
+      array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort'),
+      array('callable' => 'menu.default_tree_manipulators:flatten'),
+    );
+    $tree = $menu_tree->transform($tree, $manipulators);
+  }
+
+  $admin_tasks = array();
+  foreach ($tree as $element) {
+    if (!$element->access->isAllowed()) {
+      // @todo Bubble cacheability metadata of both accessible and inaccessible
+      //   links. Currently made impossible by the way admin tasks are rendered.
+      continue;
+    }
+
+    $link = $element->link;
+    if ($link->getProvider() != $module) {
+      continue;
+    }
+    $admin_tasks[] = array(
+      'title' => $link->getTitle(),
+      'description' => $link->getDescription(),
+      'url' => $link->getUrlObject(),
+    );
+  }
+
+  // Append link for permissions.
+  /** @var \Drupal\user\PermissionHandlerInterface $permission_handler */
+  $permission_handler = \Drupal::service('user.permissions');
+
+  if ($permission_handler->moduleProvidesPermissions($module)) {
+    /** @var \Drupal\Core\Access\AccessManagerInterface $access_manager */
+    $access_manager = \Drupal::service('access_manager');
+    if ($access_manager->checkNamedRoute('user.admin_permissions', array(), \Drupal::currentUser())) {
+      /** @var \Drupal\Core\Url $url */
+      $url = new \Drupal\Core\Url('user.admin_permissions');
+      $url->setOption('fragment', 'module-' . $module);
+      $admin_tasks["user.admin_permissions.$module"] = array(
+        'title' => t('Configure @module permissions', array('@module' => $info['name'])),
+        'description' => '',
+        'url' => $url,
+      );
+    }
+  }
+
+  return $admin_tasks;
+}
+
+/**
+ * Implements hook_cron().
+ *
+ * Remove older rows from flood, batch cache and expirable keyvalue tables.
+ */
+function system_cron() {
+  // Clean up the flood.
+  \Drupal::flood()->garbageCollection();
+
+  foreach (Cache::getBins() as $cache_backend) {
+    $cache_backend->garbageCollection();
+  }
+
+  // Clean up the expirable key value database store.
+  if (\Drupal::service('keyvalue.expirable.database') instanceof KeyValueDatabaseExpirableFactory) {
+    \Drupal::service('keyvalue.expirable.database')->garbageCollection();
+  }
+
+  // Clean up the queue for failed batches.
+  db_delete('queue')
+    ->condition('created', REQUEST_TIME - 864000, '<')
+    ->condition('name', 'drupal_batch:%', 'LIKE')
+    ->execute();
+
+  // Reset expired items in the default queue implementation table. If that's
+  // not used, this will simply be a no-op.
+  db_update('queue')
+    ->fields(array(
+      'expire' => 0,
+    ))
+    ->condition('expire', 0, '<>')
+    ->condition('expire', REQUEST_TIME, '<')
+    ->execute();
+
+  // Clean up PHP storage.
+  PhpStorageFactory::get('container')->garbageCollection();
+  PhpStorageFactory::get('service_container')->garbageCollection();
+}
+
+/**
+ * Implements hook_mail().
+ */
+function system_mail($key, &$message, $params) {
+  $token_service = \Drupal::token();
+
+  $context = $params['context'];
+
+  $subject = $token_service->replace($context['subject'], $context);
+  $body = $token_service->replace($context['message'], $context);
+
+  $message['subject'] .= str_replace(array("\r", "\n"), '', $subject);
+  $message['body'][] = $body;
+}
+
+/**
+ * Generate an array of time zones and their local time&date.
+ *
+ * @param $blank
+ *   If evaluates true, prepend an empty time zone option to the array.
+ */
+function system_time_zones($blank = NULL) {
+  $zonelist = timezone_identifiers_list();
+  $zones = $blank ? array('' => t('- None selected -')) : array();
+  foreach ($zonelist as $zone) {
+    // Because many time zones exist in PHP only for backward compatibility
+    // reasons and should not be used, the list is filtered by a regular
+    // expression.
+    if (preg_match('!^((Africa|America|Antarctica|Arctic|Asia|Atlantic|Australia|Europe|Indian|Pacific)/|UTC$)!', $zone)) {
+      $zones[$zone] = t('@zone', array('@zone' => t(str_replace('_', ' ', $zone))));
+    }
+  }
+  // Sort the translated time zones alphabetically.
+  asort($zones);
+  return $zones;
+}
+
+/**
+ * Attempts to get a file using Guzzle HTTP client and to store it locally.
+ *
+ * @param string $url
+ *   The URL of the file to grab.
+ * @param string $destination
+ *   Stream wrapper URI specifying where the file should be placed. If a
+ *   directory path is provided, the file is saved into that directory under
+ *   its original name. If the path contains a filename as well, that one will
+ *   be used instead.
+ *   If this value is omitted, the site's default files scheme will be used,
+ *   usually "public://".
+ * @param bool $managed
+ *   If this is set to TRUE, the file API hooks will be invoked and the file is
+ *   registered in the database.
+ * @param int $replace
+ *   Replace behavior when the destination file already exists:
+ *   - FILE_EXISTS_REPLACE: Replace the existing file.
+ *   - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename is
+ *     unique.
+ *   - FILE_EXISTS_ERROR: Do nothing and return FALSE.
+ *
+ * @return mixed
+ *   One of these possibilities:
+ *   - If it succeeds and $managed is FALSE, the location where the file was
+ *     saved.
+ *   - If it succeeds and $managed is TRUE, a \Drupal\file\FileInterface
+ *     object which describes the file.
+ *   - If it fails, FALSE.
+ */
+function system_retrieve_file($url, $destination = NULL, $managed = FALSE, $replace = FILE_EXISTS_RENAME) {
+  $parsed_url = parse_url($url);
+  if (!isset($destination)) {
+    $path = file_build_uri(drupal_basename($parsed_url['path']));
+  }
+  else {
+    if (is_dir(drupal_realpath($destination))) {
+      // Prevent URIs with triple slashes when glueing parts together.
+      $path = str_replace('///', '//', "$destination/") . drupal_basename($parsed_url['path']);
+    }
+    else {
+      $path = $destination;
+    }
+  }
+  try {
+    $data = (string) \Drupal::httpClient()
+      ->get($url)
+      ->getBody();
+    $local = $managed ? file_save_data($data, $path, $replace) : file_unmanaged_save_data($data, $path, $replace);
+  }
+  catch (RequestException $exception) {
+    drupal_set_message(t('Failed to fetch file due to error "%error"', array('%error' => $exception->getMessage())), 'error');
+    return FALSE;
+  }
+  if (!$local) {
+    drupal_set_message(t('@remote could not be saved to @path.', array('@remote' => $url, '@path' => $path)), 'error');
+  }
+
+  return $local;
+}
+
+/**
+ * Implements hook_entity_type_build().
+ */
+function system_entity_type_build(array &$entity_types) {
+  /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
+  $entity_types['date_format']
+    ->setFormClass('add', 'Drupal\system\Form\DateFormatAddForm')
+    ->setFormClass('edit', 'Drupal\system\Form\DateFormatEditForm')
+    ->setFormClass('delete', 'Drupal\system\Form\DateFormatDeleteForm')
+    ->setListBuilderClass('Drupal\system\DateFormatListBuilder')
+    ->setLinkTemplate('edit-form', '/admin/config/regional/date-time/formats/manage/{date_format}')
+    ->setLinkTemplate('delete-form', '/admin/config/regional/date-time/formats/manage/{date_format}/delete')
+    ->setLinkTemplate('collection', '/admin/config/regional/date-time/formats');
+}
+
+/**
+ * Implements hook_block_view_BASE_BLOCK_ID_alter().
+ */
+function system_block_view_system_main_block_alter(array &$build, BlockPluginInterface $block) {
+  // Contextual links on the system_main block would basically duplicate the
+  // tabs/local tasks, so reduce the clutter.
+  unset($build['#contextual_links']);
+}
+
+/**
+ * Implements hook_path_update().
+ */
+function system_path_update() {
+  \Drupal::service('path.alias_manager')->cacheClear();
+}
+
+/**
+ * Implements hook_path_insert().
+ */
+function system_path_insert() {
+  \Drupal::service('path.alias_manager')->cacheClear();
+}
+
+/**
+ * Implements hook_path_delete().
+ */
+function system_path_delete($path) {
+  \Drupal::service('path.alias_manager')->cacheClear();
+}
diff --git a/core/modules/system/templates/system-project-details.html.twig b/core/modules/system/templates/system-project-details.html.twig
new file mode 100644
index 0000000..a0c7f73
--- /dev/null
+++ b/core/modules/system/templates/system-project-details.html.twig
@@ -0,0 +1,29 @@
+{#
+/**
+ * @file
+ * Default theme implementation for the Project details.
+ *
+ * Available variables:
+ * - attributes: HTML attributes for the main container.
+ * - machine_name: The machine name of the project.
+ * - version: The version of the project.
+ * - requires: The names of projects required by this project.
+ * - required_by: The projects that are requires this project.
+ *
+ * @ingroup themeable
+ */
+#}
+<div{{ attributes }}>
+  {% if machine_name %}
+    <div>{{ 'Machine name: @machine-name'|t({'<@></@>machine-name': machine_name}) }}</div>
+  {% endif %}
+  {% if version %}
+    <div>{{ 'Version: @version'|t({'@version': version}) }}</div>
+  {% endif %}
+  {% if requires %}
+    <div>{{ 'Requires: @extensions'|t({'@extensions': requires|safe_join(', ')}) }}</div>
+  {% endif %}
+  {% if required_by %}
+    <div>{{ 'Required by: @extensions'|t({'@extensions': required_by|safe_join(', ')}) }}</div>
+  {% endif %}
+</div>
diff --git a/core/modules/system/templates/system-themes-page.html.twig b/core/modules/system/templates/system-themes-page.html.twig
index 6e65d76..0173331 100644
--- a/core/modules/system/templates/system-themes-page.html.twig
+++ b/core/modules/system/templates/system-themes-page.html.twig
@@ -56,7 +56,7 @@
           {% endif %}
           <div class="theme-info">
             <h3 class="theme-info__header">
-              {{- theme.name }} {{ theme.version -}}
+              {{- theme.name -}}
               {% if theme.notes %}
                 ({{ theme.notes|safe_join(', ') }})
               {%- endif -%}
diff --git a/core/themes/classy/templates/misc/system-project-details.html.twig b/core/themes/classy/templates/misc/system-project-details.html.twig
new file mode 100644
index 0000000..7c9f072
--- /dev/null
+++ b/core/themes/classy/templates/misc/system-project-details.html.twig
@@ -0,0 +1,30 @@
+
+{#
+/**
+ * @file
+ * Theme override for the Project details.
+ *
+ * Available variables:
+ * - attributes: HTML attributes for the main container.
+ * - machine_name: The machine name of the project.
+ * - version: The version of the project.
+ * - requires: The names of projects required by this project.
+ * - required_by: The projects that are requires this project.
+ *
+ * @ingroup themeable
+ */
+#}
+<div class="requirements" {{ attributes }}>
+  {% if machine_name %}
+    <div class="admin-requirements">{{ 'Machine name: !machine-name'|t({'!machine-name': machine_name}) }}</div>
+  {% endif %}
+  {% if version %}
+    <div class="admin-requirements">{{ 'Version: !version'|t({'!version': version}) }}</div>
+  {% endif %}
+  {% if requires %}
+    <div class="admin-requirements">{{ 'Requires: @extensions'|t({'@extensions': requires|join(', ')}) }}</div>
+  {% endif %}
+  {% if required_by %}
+    <div class="admin-requirements">{{ 'Required by: @extensions'|t({'@extensions': required_by|join(', ')}) }}</div>
+  {% endif %}
+</div>
diff --git a/core/themes/seven/css/components/modules-page.css b/core/themes/seven/css/components/modules-page.css
index bc9c1e5..57c65bb 100644
--- a/core/themes/seven/css/components/modules-page.css
+++ b/core/themes/seven/css/components/modules-page.css
@@ -1,47 +1,48 @@
 /* Modules page */
-.system-modules fieldset {
+.system-projects fieldset {
   border: 0;
   border-top: 1px solid #ccc;
 }
-.system-modules details {
+.system-projects details {
   border: 0;
   margin: 0;
   padding: 0;
 }
-.system-modules summary {
+.system-projects summary {
   border-bottom: 1px solid #ccc;
 }
-.system-modules [open] summary {
+.system-projects .description summary,
+.system-projects [open] summary {
   border-bottom: none;
 }
-.system-modules .details-wrapper {
+.system-projects .details-wrapper {
   padding: 0 0 0.5em 0;
 }
-.system-modules .fieldset-wrapper {
+.system-projects .fieldset-wrapper {
   padding: 0;
 }
-.system-modules table,
+.system-projects table,
 .locale-translation-status-form table {
   border: 0;
 }
-.system-modules tr.even,
-.system-modules tr.odd,
+.system-projects tr.even,
+.system-projects tr.odd,
 .locale-translation-status-form tr.even,
 .locale-translation-status-form tr.odd {
   background: #f3f4ee;
   border: 0;
   border-bottom: 10px solid #fff;
 }
-.system-modules tr td:last-child,
+.system-projects tr td:last-child,
 .locale-translation-status-form tr td:last-child {
   border: 0;
 }
-.system-modules table th,
+.system-projects table th,
 .locale-translation-status-form table th {
   border: 0;
   border-bottom: 10px solid #fff;
 }
-.system-modules .sticky-header th,
+.system-projects .sticky-header th,
 .locale-translation-status-form .sticky-header th {
   border: 0;
 }
