diff --git a/core/includes/form.inc b/core/includes/form.inc
index 665a9e6..83bfeff 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -1779,6 +1779,12 @@ function form_builder($form_id, &$element, &$form_state) {
   if (!isset($element['#id'])) {
     $element['#id'] = drupal_html_id('edit-' . implode('-', $element['#parents']));
   }
+
+  // Add the aria-describedby attribute to associate the form control with its
+  // description.
+  if (!empty($element['#description'])) {
+    $element['#attributes']['aria-describedby'] = $element['#id'] . '--description';
+  }
   // Handle input elements.
   if (!empty($element['#input'])) {
     _form_builder_handle_input_element($form_id, $element, $form_state);
@@ -3298,7 +3304,21 @@ function theme_tableselect($variables) {
       // As theme_table only maps header and row columns by order, create the
       // correct order by iterating over the header fields.
       foreach ($element['#header'] as $fieldname => $title) {
-        $row['data'][] = $element['#options'][$key][$fieldname];
+        // A row cell can span over multiple headers, which means less row cells
+        // than headers could be present.
+        if (isset($element['#options'][$key][$fieldname])) {
+          // A header can span over multiple cells and in this case the cells
+          // are passed in an array. The order of this array determines the
+          // order in which they are added.
+          if (!isset($element['#options'][$key][$fieldname]['data']) && is_array($element['#options'][$key][$fieldname])) {
+            foreach ($element['#options'][$key][$fieldname] as $cell) {
+              $row['data'][] = $cell;
+            }
+          }
+          else {
+            $row['data'][] = $element['#options'][$key][$fieldname];
+          }
+        }
       }
       $rows[] = $row;
     }
@@ -4186,7 +4206,11 @@ function theme_form_element($variables) {
   }
 
   if (!empty($element['#description'])) {
-    $output .= '<div class="description">' . $element['#description'] . "</div>\n";
+    $attributes = array('class' => 'description');
+    if (!empty($element['#id'])) {
+      $attributes['id'] = $element['#id'] . '--description';
+    }
+    $output .= '<div' . drupal_attributes($attributes) . '>' . $element['#description'] . "</div>\n";
   }
 
   $output .= "</div>\n";
diff --git a/core/includes/install.inc b/core/includes/install.inc
index 199ec1c..72adf1c 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -1,6 +1,7 @@
 <?php
 
 use Drupal\Core\Database\Database;
+use Drupal\Core\Config\SignedFileStorage;
 
 /**
  * Indicates that a module has not been installed yet.
@@ -393,6 +394,22 @@ function drupal_uninstall_modules($module_list = array(), $uninstall_dependents
     module_invoke($module, 'uninstall');
     drupal_uninstall_schema($module);
 
+    // Remove any stray configuration settings.
+    // Get the names of default configurations provided by this module
+    // by scanning its config directory.
+    $module_config_dir = drupal_get_path('module', $module) . '/config';
+    if (is_dir($module_config_dir)) {
+      $files = glob($module_config_dir . '/' . '*.xml');
+      foreach ($files as $file) {
+        $parts = explode('/', $file);
+        $file = array_pop($parts);
+        $config_name = str_replace('.xml', '', $file);
+        $signed_storage = new SignedFileStorage($config_name);
+        // Delete the configuration from storage.
+        $signed_storage->delete();
+      }
+    }
+
     watchdog('system', '%module module uninstalled.', array('%module' => $module), WATCHDOG_INFO);
     drupal_set_installed_schema_version($module, SCHEMA_UNINSTALLED);
   }
diff --git a/core/includes/mail.inc b/core/includes/mail.inc
index f58a76e..214ebc6 100644
--- a/core/includes/mail.inc
+++ b/core/includes/mail.inc
@@ -118,9 +118,17 @@ define('MAIL_LINE_ENDINGS', isset($_SERVER['WINDIR']) || strpos($_SERVER['SERVER
  *   accepted at php-level, which still doesn't guarantee it to be delivered.)
  */
 function drupal_mail($module, $key, $to, $language, $params = array(), $from = NULL, $send = TRUE) {
-  $default_from = variable_get('site_mail', ini_get('sendmail_from'));
+  // Gather info about the module's mails by invoking hook_mail().
+  $info = module_invoke($module, 'mail', $language);
+  if (!isset($info[$key])) {
+    // The mail key was not defined.
+    watchdog('mail', 'Mail key %key does not exist in the %module module', array('%key' => $key, '%module' => $module), WATCHDOG_WARNING);
+    drupal_set_message(t('Unable to send e-mail. %key is not defined. Contact the site administrator if the problem persists.', array('%key' => $key)), 'error');
+    return array('result' => FALSE);
+  }
 
-  // Bundle up the variables into a structured array for altering.
+  // Build defaults.
+  $default_from = variable_get('site_mail', ini_get('sendmail_from'));
   $message = array(
     'id'       => $module . '_' . $key,
     'module'   => $module,
@@ -128,10 +136,12 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N
     'to'       => $to,
     'from'     => isset($from) ? $from : $default_from,
     'language' => $language,
-    'params'   => $params,
     'send'     => TRUE,
-    'subject'  => '',
-    'body'     => array()
+    'subject callback' => 't',
+    'body callback' => 't',
+    'subject arguments' => array(''),
+    'body arguments' => array(''),
+    'params' => array(),
   );
 
   // Build the default headers
@@ -151,17 +161,21 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N
     $headers['From'] = $from;
   }
   $message['headers'] = $headers;
+  $message = array_merge($message, $info[$key]);
+  $message['params'] = array_merge($message['params'], $params);
 
-  // Build the e-mail (get subject and body, allow additional headers) by
-  // invoking hook_mail() on this module. We cannot use module_invoke() as
-  // we need to have $message by reference in hook_mail().
-  if (function_exists($function = $module . '_mail')) {
-    $function($key, $message, $params);
-  }
-
-  // Invoke hook_mail_alter() to allow all modules to alter the resulting e-mail.
+  // Invoke hook_mail_alter() to allow all modules to alter the resulting mail.
   drupal_alter('mail', $message);
 
+  // Invoke subject and body callbacks
+  foreach (array('subject', 'body') as $part) {
+    $function = $message[$part . ' callback'];
+    if (function_exists($function)) {
+      $args = array_merge($message[$part . ' arguments'], array($message['params']));
+      $message[$part] = call_user_func_array($function, $args);
+    }
+  }
+
   // Retrieve the responsible implementation for this message.
   $system = drupal_mail_system($module, $key);
 
@@ -171,8 +185,8 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N
   // Optionally send e-mail.
   if ($send) {
     // The original caller requested sending. Sending was canceled by one or
-    // more hook_mail_alter() implementations. We set 'result' to NULL, because
-    // FALSE indicates an error in sending.
+    // more hook_mail() or hook_mail_alter() implementations. We set 'result'
+    // to NULL, because FALSE indicates an error in sending.
     if (empty($message['send'])) {
       $message['result'] = NULL;
     }
diff --git a/core/lib/Drupal/Core/Mail/PhpMail.php b/core/lib/Drupal/Core/Mail/PhpMail.php
index 668cc51..df45ef9 100644
--- a/core/lib/Drupal/Core/Mail/PhpMail.php
+++ b/core/lib/Drupal/Core/Mail/PhpMail.php
@@ -22,8 +22,6 @@ class PhpMail implements MailInterface {
    *   The formatted $message.
    */
   public function format(array $message) {
-    // Join the body array into one string.
-    $message['body'] = implode("\n\n", $message['body']);
     // Convert any HTML to plain-text.
     $message['body'] = drupal_html_to_text($message['body']);
     // Wrap the mail body for sending.
diff --git a/core/modules/contact/contact.module b/core/modules/contact/contact.module
index 89ce0bc..c293ed3 100644
--- a/core/modules/contact/contact.module
+++ b/core/modules/contact/contact.module
@@ -167,43 +167,73 @@ function contact_load($cid) {
 /**
  * Implements hook_mail().
  */
-function contact_mail($key, &$message, $params) {
-  $language = $message['language'];
-  $variables = array(
-    '!site-name' => variable_get('site_name', 'Drupal'),
-    '!subject' => $params['subject'],
-    '!category' => isset($params['category']['category']) ? $params['category']['category'] : '',
-    '!form-url' => url($_GET['q'], array('absolute' => TRUE, 'language' => $language)),
-    '!sender-name' => user_format_name($params['sender']),
-    '!sender-url' => $params['sender']->uid ? url('user/' . $params['sender']->uid, array('absolute' => TRUE, 'language' => $language)) : $params['sender']->mail,
+function contact_mail($language = NULL) {
+  foreach (array('page_mail', 'page_copy') as $key) {
+    $mail[$key] = array(
+      'subject arguments' => array('[!category] !subject'),
+      'body arguments' => array(_contact_mail_body($key)),
+      'params' => array(
+        '!category' => '',
+        '!subject' => '',
+        '!sender-name' => variable_get('site_name', 'Drupal'),
+        '!sender-url' => '',
+        '!form-url' => url($_GET['q'], array('absolute' => TRUE, 'language' => $language)),
+        '!message' => '',
+      ),
+    );
+  }
+
+  $mail['page_autoreply'] = array(
+    'subject arguments' => array('[!category] !subject'),
+    'body arguments' => array(_contact_mail_body('page_autoreply')),
+    'params' => array(
+      '!category' => '',
+      '!subject' => '',
+      '!category-reply' => '',
+    ),
   );
 
+  foreach (array('user_mail', 'user_copy') as $key) {
+    $mail[$key] = array(
+      'subject arguments' => array('[!site-name] !subject'),
+      'body arguments' => array(_contact_mail_body($key)),
+      'params' => array(
+        '!site-name' => variable_get('site_name', 'Drupal'),
+        '!subject' => '',
+        '!recipient-name' => '',
+        '!sender-name' => '',
+        '!sender-url' => '',
+        '!form-url' => url($_GET['q'], array('absolute' => TRUE, 'language' => $language)),
+        '!recipient-edit-url' => array(),
+      ),
+    );
+  }
+
+  return $mail;
+}
+
+function _contact_mail_body($key) {
   switch ($key) {
     case 'page_mail':
     case 'page_copy':
-      $message['subject'] .= t('[!category] !subject', $variables, array('langcode' => $language->langcode));
-      $message['body'][] = t("!sender-name (!sender-url) sent a message using the contact form at !form-url.", $variables, array('langcode' => $language->langcode));
-      $message['body'][] = $params['message'];
-      break;
+      return "!sender-name (!sender-url) sent a message using the contact form at !form-url.
+
+!message";
 
     case 'page_autoreply':
-      $message['subject'] .= t('[!category] !subject', $variables, array('langcode' => $language->langcode));
-      $message['body'][] = $params['category']['reply'];
-      break;
+      return "!category-reply";
 
     case 'user_mail':
     case 'user_copy':
-      $variables += array(
-        '!recipient-name' => user_format_name($params['recipient']),
-        '!recipient-edit-url' => url('user/' . $params['recipient']->uid . '/edit', array('absolute' => TRUE, 'language' => $language)),
-      );
-      $message['subject'] .= t('[!site-name] !subject', $variables, array('langcode' => $language->langcode));
-      $message['body'][] = t('Hello !recipient-name,', $variables, array('langcode' => $language->langcode));
-      $message['body'][] = t("!sender-name (!sender-url) has sent you a message via your contact form (!form-url) at !site-name.", $variables, array('langcode' => $language->langcode));
-      $message['body'][] = t("If you don't want to receive such e-mails, you can change your settings at !recipient-edit-url.", $variables, array('langcode' => $language->langcode));
-      $message['body'][] = t('Message:', array(), array('langcode' => $language->langcode));
-      $message['body'][] = $params['message'];
-      break;
+      return "Hello !recipient-name,
+
+!sender-name (!sender-url) has sent you a message via your contact form (!form-url) at !site-name.
+
+If you don't want to receive such e-mails, you can change your settings at !recipient-edit-url.
+
+Message:
+
+!message";
   }
 }
 
diff --git a/core/modules/contact/contact.pages.inc b/core/modules/contact/contact.pages.inc
index bf096a7..34df524 100644
--- a/core/modules/contact/contact.pages.inc
+++ b/core/modules/contact/contact.pages.inc
@@ -131,10 +131,7 @@ function contact_site_form_submit($form, &$form_state) {
   global $user, $language_interface;
 
   $values = $form_state['values'];
-  $values['sender'] = $user;
-  $values['sender']->name = $values['name'];
-  $values['sender']->mail = $values['mail'];
-  $values['category'] = contact_load($values['cid']);
+  $category = contact_load($values['cid']);
 
   // Save the anonymous user information to a cookie for reuse.
   if (!$user->uid) {
@@ -142,24 +139,33 @@ function contact_site_form_submit($form, &$form_state) {
   }
 
   // Get the to and from e-mail addresses.
-  $to = $values['category']['recipients'];
-  $from = $values['sender']->mail;
+  $to = $category['recipients'];
+  $from = $values['mail'];
+
+  $params = array(
+    '!subject' => $values['subject'],
+    '!category' => isset($category['category']) ? $category['category'] : '',
+    '!sender-name' => user_format_name($user),
+    '!sender-url' => $user->uid ? url('user/' . $user->uid, array('absolute' => TRUE, 'language' => language_default())) : $values['mail'],
+    '!message' => $values['message'],
+    '!category-reply' => isset($category['reply']) ? $category['reply'] : '',
+  );
 
   // Send the e-mail to the recipients using the site default language.
-  drupal_mail('contact', 'page_mail', $to, language_default(), $values, $from);
+  drupal_mail('contact', 'page_mail', $to, language_default(), $params, $from);
 
   // If the user requests it, send a copy using the current language.
   if ($values['copy']) {
-    drupal_mail('contact', 'page_copy', $from, $language_interface, $values, $from);
+    drupal_mail('contact', 'page_copy', $from, $language_interface, $params, $from);
   }
 
   // Send an auto-reply if necessary using the current language.
-  if ($values['category']['reply']) {
-    drupal_mail('contact', 'page_autoreply', $from, $language_interface, $values, $to);
+  if ($category['reply']) {
+    drupal_mail('contact', 'page_autoreply', $from, $language_interface, $params, $to);
   }
 
   flood_register_event('contact', variable_get('contact_threshold_window', 3600));
-  watchdog('mail', '%sender-name (@sender-from) sent an e-mail regarding %category.', array('%sender-name' => $values['name'], '@sender-from' => $from, '%category' => $values['category']['category']));
+  watchdog('mail', '%sender-name (@sender-from) sent an e-mail regarding %category.', array('%sender-name' => $values['name'], '@sender-from' => $from, '%category' => $category['category']['category']));
 
   // Jump to home page rather than back to contact page to avoid
   // contradictory messages if flood control has been activated.
@@ -253,9 +259,6 @@ function contact_personal_form_submit($form, &$form_state) {
   global $user, $language_interface;
 
   $values = $form_state['values'];
-  $values['sender'] = $user;
-  $values['sender']->name = $values['name'];
-  $values['sender']->mail = $values['mail'];
 
   // Save the anonymous user information to a cookie for reuse.
   if (!$user->uid) {
@@ -264,14 +267,22 @@ function contact_personal_form_submit($form, &$form_state) {
 
   // Get the to and from e-mail addresses.
   $to = $values['recipient']->mail;
-  $from = $values['sender']->mail;
+  $from = $user->mail;
+
+  $params = array(
+    '!subject' => $values['subject'],
+    '!sender-name' => user_format_name($user),
+    '!sender-url' => $user->uid ? url('user/' . $user->uid, array('absolute' => TRUE, 'language' => language_default())) : $values['mail'],
+    '!recipient-name' => user_format_name($values['recipient']),
+    '!recipient-edit-url' => url('user/' . $values['recipient']->uid . '/edit', array('absolute' => TRUE, 'language' => user_preferred_language($values['recipient']))),
+  );
 
   // Send the e-mail in the requested user language.
-  drupal_mail('contact', 'user_mail', $to, user_preferred_language($values['recipient']), $values, $from);
+  drupal_mail('contact', 'user_mail', $to, user_preferred_language($values['recipient']), $params, $from);
 
   // Send a copy if requested, using current page language.
   if ($values['copy']) {
-    drupal_mail('contact', 'user_copy', $from, $language_interface, $values, $from);
+    drupal_mail('contact', 'user_copy', $from, $language_interface, $params, $from);
   }
 
   flood_register_event('contact', variable_get('contact_threshold_window', 3600));
diff --git a/core/modules/contextual/contextual.base.css b/core/modules/contextual/contextual.base.css
index 0b59eba..5c20c8a 100644
--- a/core/modules/contextual/contextual.base.css
+++ b/core/modules/contextual/contextual.base.css
@@ -13,8 +13,7 @@
   display: none;
 }
 .contextual-region:hover .contextual,
-.contextual-region:hover .trigger,
-.contextual-active .trigger,
+.contextual-region:hover .contextual-links-trigger-active,
 .contextual-active .contextual-links {
   display: block;
 }
diff --git a/core/modules/contextual/contextual.js b/core/modules/contextual/contextual.js
index 2744acb..e3968f3 100644
--- a/core/modules/contextual/contextual.js
+++ b/core/modules/contextual/contextual.js
@@ -30,6 +30,10 @@ Drupal.behaviors.contextualLinks = {
       );
       // Hide the contextual links when user clicks a link or rolls out of the .contextual-region.
       $region.bind('mouseleave click', Drupal.contextualLinks.mouseleave);
+      $region.hover(
+        function() { $trigger.addClass('contextual-links-trigger-active'); },
+        function() { $trigger.removeClass('contextual-links-trigger-active'); }
+      );
       // Prepend the trigger.
       $wrapper.prepend($trigger);
     });
diff --git a/core/modules/filter/filter.admin-rtl.css b/core/modules/filter/filter.admin-rtl.css
new file mode 100644
index 0000000..9fa9f42
--- /dev/null
+++ b/core/modules/filter/filter.admin-rtl.css
@@ -0,0 +1,19 @@
+
+/**
+ * @file
+ * RTL admin styling for the filter module.
+ */
+
+/**
+ * Filter information under field.
+ */
+.filter-wrapper .form-item {
+  float: right;
+  padding: 0 1.5em 0.5em 0;
+}
+.filter-help {
+  float: left;
+}
+.filter-help a {
+  background-position: left center;
+}
diff --git a/core/modules/filter/filter.admin.css b/core/modules/filter/filter.admin.css
new file mode 100644
index 0000000..d6004d5
--- /dev/null
+++ b/core/modules/filter/filter.admin.css
@@ -0,0 +1,52 @@
+
+/**
+ * @file
+ * Admin styling for the filter module.
+ */
+
+/**
+ * Filter information under field.
+ */
+.text-format-wrapper .form-item {
+  margin-bottom: 0;
+}
+
+.filter-wrapper {
+  border-top: 0;
+  margin: 0;
+  padding: 1.5em 0;
+}
+.filter-wrapper .form-item {
+  float: left; /* LTR */
+  padding: 0 0 0.5em 1.5em; /* LTR */
+}
+.filter-wrapper .form-item label {
+  display: inline;
+}
+
+.filter-help {
+  float: right; /* LTR */
+  padding: 0 1.5em 0.5em;
+}
+.filter-help p {
+  margin: 0;
+}
+.filter-help a {
+  background: transparent url(../../misc/help.png) right center no-repeat; /* LTR */
+  padding: 0 20px;
+}
+
+.filter-guidelines {
+  clear: left;
+  padding: 0 1.5em;
+}
+.text-format-wrapper .description {
+  margin-top: 0.5em;
+}
+.tips {
+  font-size: 0.9em;
+  margin-bottom: 0;
+  margin-top: 0;
+  padding-bottom: 0;
+  padding-top: 0;
+}
diff --git a/core/modules/simpletest/tests/form.test b/core/modules/simpletest/tests/form.test
index e79983c..87d3b51 100644
--- a/core/modules/simpletest/tests/form.test
+++ b/core/modules/simpletest/tests/form.test
@@ -842,6 +842,31 @@ class FormsElementsTableSelectFunctionalTest extends DrupalWebTestCase {
   }
 
   /**
+   * Tests the display when #colspan is set.
+   */
+  function testTableselectColSpan() {
+    $this->drupalGet('form_test/tableselect/colspan');
+
+    $this->assertText(t('Three'), 'Presence of the third column');
+    $this->assertNoText(t('Four'), 'Absence of a fourth column');
+
+    // There should be three labeled column headers and 1 for the input.
+    $table_head = $this->xpath('//thead');
+    $this->assertEqual(count($table_head[0]->tr->th), 4, 'There are four column headers');
+
+    $table_body = $this->xpath('//tbody');
+    // The first two body rows should each have 5 table cells: One for the
+    // radio, one cell in the first column, one cell in the the second column,
+    // and two cells in the third column which has colspan 2.
+    for ( $i = 0; $i <= 1; $i++) {
+      $this->assertEqual(count($table_body[0]->tr[$i]->td), 5, format_string('There are five cells in row @row.', array('@row' => $i)));
+    }
+    // The third row should have 3 cells, one for the radio, one spanning the
+    // first and second column, and a third in column 3 (which has colspan 3).
+    $this->assertEqual(count($table_body[0]->tr[2]->td), 3, 'There are three cells in row 3.');
+  }
+
+  /**
    * Test the display of the #empty text when #options is an empty array.
    */
   function testEmptyText() {
diff --git a/core/modules/simpletest/tests/form_test.module b/core/modules/simpletest/tests/form_test.module
index 3dcd247..9651bf2 100644
--- a/core/modules/simpletest/tests/form_test.module
+++ b/core/modules/simpletest/tests/form_test.module
@@ -52,6 +52,13 @@ function form_test_menu() {
     'access callback' => TRUE,
     'type' => MENU_CALLBACK,
   );
+  $items['form_test/tableselect/colspan'] = array(
+    'title' => 'Tableselect colspan test',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('_form_test_tableselect_colspan_form'),
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+  );
   $items['form_test/tableselect/empty-text'] = array(
     'title' => 'Tableselect empty text test',
     'page callback' => 'drupal_get_form',
@@ -592,6 +599,29 @@ function _form_test_tableselect_multiple_false_form($form, $form_state) {
 }
 
 /**
+ * Test the tableselect #colspan functionality.
+ */
+function _form_test_tableselect_colspan_form($form, $form_state) {
+  list($header, $options) = _form_test_tableselect_get_data();
+
+  // Change the data so that the third column has colspan=2.
+  $header['three'] = array('data' => 'Three', 'colspan' => 2);
+  unset($header['four']);
+  // Set the each row so that column 3 is an array.
+  foreach ($options as $name => $row) {
+    $options[$name]['three'] = array($row['three'], $row['four']);
+    unset($options[$name]['four']);
+  }
+  // Combine cells in row 3.
+  $options['row3']['one'] = array('data' => $options['row3']['one'], 'colspan' => 2);
+  unset($options['row3']['two']);
+  $options['row3']['three'] = array('data' => $options['row3']['three'][0], 'colspan' => 2);
+  unset($options['row3']['four']);
+
+  return _form_test_tableselect_form_builder($form, $form_state, array('#header' => $header, '#options' => $options));
+}
+
+/**
  * Process the tableselect #multiple = FALSE submitted values.
  */
 function _form_test_tableselect_multiple_false_form_submit($form, &$form_state) {
diff --git a/core/modules/system/system.test b/core/modules/system/system.test
index 9287d16..099c673 100644
--- a/core/modules/system/system.test
+++ b/core/modules/system/system.test
@@ -73,6 +73,66 @@ class ModuleTestCase extends DrupalWebTestCase {
   }
 
   /**
+   * Assert that a module's config files have been loaded.
+   *
+   * @param string $module
+   *   The name of the module.
+   *
+   * @return bool
+   *   TRUE if the module's config files exist, FALSE otherwise.
+   */
+  function assertModuleConfigFilesExist($module) {
+    // Define test variable.
+    $files_exist = TRUE;
+    // Get the path to the module's config dir.
+    $module_config_dir = drupal_get_path('module', $module) . '/config';
+    if (is_dir($module_config_dir)) {
+      $files = glob($module_config_dir . '/' . '*.xml');
+      $config_dir = config_get_config_directory();
+      // Get the filename of each config file.
+      foreach ($files as $file) {
+        $parts = explode('/', $file);
+        $filename = array_pop($parts);
+        if (!file_exists($config_dir . '/' . $filename)) {
+          $files_exist = FALSE;
+        }
+      }
+    }
+
+    return $this->assertTrue($files_exist, t('All config files defined by the @module module have been copied to the live config directory.', array('@module' => $module)));
+  }
+
+  /**
+   * Assert that none of a module's default config files are loaded.
+   *
+   * @param string $module
+   *   The name of the module.
+   *
+   * @return bool
+   *   TRUE if the module's config files do not exist, FALSE otherwise.
+   */
+  function assertModuleConfigFilesDoNotExist($module) {
+    // Define test variable.
+    $files_exist = FALSE;
+    // Get the path to the module's config dir.
+    $module_config_dir = drupal_get_path('module', $module) . '/config';
+    if (is_dir($module_config_dir)) {
+      $files = glob($module_config_dir . '/' . '*.xml');
+      $config_dir = config_get_config_directory();
+      // Get the filename of each config file.
+      foreach ($files as $file) {
+        $parts = explode('/', $file);
+        $filename = array_pop($parts);
+        if (file_exists($config_dir . '/' . $filename)) {
+          $files_exist = TRUE;
+        }
+      }
+    }
+
+    return $this->assertFalse($files_exist, t('All config files defined by the @module module have been deleted from the live config directory.', array('@module' => $module)));
+  }
+
+  /**
    * Assert the list of modules are enabled or disabled.
    *
    * @param $modules
@@ -227,6 +287,7 @@ class EnableDisableTestCase extends ModuleTestCase {
           $this->assertText(t('hook_modules_enabled fired for @module', array('@module' => $module_to_enable)));
           $this->assertModules(array($module_to_enable), TRUE);
           $this->assertModuleTablesExist($module_to_enable);
+          $this->assertModuleConfigFilesExist($module_to_enable);
           $this->assertLogMessage('system', "%module module installed.", array('%module' => $module_to_enable), WATCHDOG_INFO);
           $this->assertLogMessage('system', "%module module enabled.", array('%module' => $module_to_enable), WATCHDOG_INFO);
         }
@@ -306,6 +367,8 @@ class EnableDisableTestCase extends ModuleTestCase {
 
     //  Check that the module's database tables still exist.
     $this->assertModuleTablesExist($module);
+    //  Check that the module's config files still exist.
+    $this->assertModuleConfigFilesExist($module);
 
     // Uninstall the module.
     $edit = array();
@@ -326,6 +389,8 @@ class EnableDisableTestCase extends ModuleTestCase {
 
     // Check that the module's database tables no longer exist.
     $this->assertModuleTablesDoNotExist($module);
+    // Check that the module's config files no longer exist.
+    $this->assertModuleConfigFilesDoNotExist($module);
   }
 }
 
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 865f17b..69e8862 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -2570,11 +2570,32 @@ function user_build_content($account, $view_mode = 'full', $langcode = NULL) {
 /**
  * Implements hook_mail().
  */
-function user_mail($key, &$message, $params) {
-  $language = $message['language'];
-  $variables = array('user' => $params['account']);
-  $message['subject'] .= _user_mail_text($key . '_subject', $language, $variables);
-  $message['body'][] = _user_mail_text($key . '_body', $language, $variables);
+function user_mail($language = NULL) {
+  $keys = array(
+    'register_no_approval_required',
+    'register_admin_created',
+    'register_pending_approval',
+    'register_pending_approval_admin',
+    'password_reset',
+    'status_activated',
+    'status_blocked',
+    'cancel_confirm',
+    'status_canceled',
+  );
+
+  $mail = array();
+  foreach ($keys as $key) {
+    $mail[$key] = array(
+      'subject callback' => '_user_mail_text',
+      'body callback' => '_user_mail_text',
+      'subject arguments' => array($key . '_subject', $language, TRUE),
+      'body arguments' => array($key . '_body', $language, TRUE),
+      'params' => array(
+        'account' => new stdClass,
+      ),
+    );
+  }
+  return $mail;
 }
 
 /**
@@ -2582,7 +2603,7 @@ function user_mail($key, &$message, $params) {
  *
  * Used by user_mail() and the settings forms to retrieve strings.
  */
-function _user_mail_text($key, $language = NULL, $variables = array(), $replace = TRUE) {
+function _user_mail_text($key, $language, $replace, $params) {
   $langcode = isset($language) ? $language->langcode : NULL;
 
   if ($admin_setting = variable_get('user_mail_' . $key, FALSE)) {
@@ -2735,7 +2756,7 @@ Your account on [site:name] has been canceled.
   if ($replace) {
     // We do not sanitize the token replacement, since the output of this
     // replacement is intended for an e-mail message, not a web browser.
-    return token_replace($text, $variables, array('language' => $language, 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
+    return token_replace($text, array('user' => $params['account']), array('language' => $language, 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
   }
 
   return $text;
