diff --git a/core/modules/token/token.info b/core/modules/token/token.info
new file mode 100644
index 0000000..de910b6
--- /dev/null
+++ b/core/modules/token/token.info
@@ -0,0 +1,5 @@
+name = Token
+description = Provides a user interface for tokens.
+package = Core
+version = VERSION
+core = 8.x
diff --git a/core/modules/token/token.js b/core/modules/token/token.js
new file mode 100644
index 0000000..e0755fc
--- /dev/null
+++ b/core/modules/token/token.js
@@ -0,0 +1,77 @@
+/**
+ * @file
+ * JavaScript behaviors for the token selector tree.
+ */
+
+(function ($) {
+
+Drupal.behaviors.tokenDialog = {
+  attach: function (context, settings) {
+    $('a.token-dialog', context).once('token-dialog').click(function() {
+      var url = $(this).attr('href');
+      var dialog = $('<div style="display: none" class="loading">' + Drupal.t('Loading token browser...') + '</div>').appendTo('body');
+      dialog.dialog({
+        title: $(this).attr('title') || Drupal.t('Available tokens'),
+        width: 700,
+        close: function(event, ui) {
+          dialog.remove();
+        }
+      });
+      // Load the token tree using AJAX.
+      dialog.load(
+        url,
+        {},
+        function (responseText, textStatus, XMLHttpRequest) {
+          dialog.removeClass('loading');
+        }
+      );
+      // Prevent browser from following the link.
+      return false;
+    });
+  }
+}
+
+Drupal.behaviors.tokenInsert = {
+  attach: function (context, settings) {
+    // Keep track of which textfield was last selected/focused.
+    $('textarea, input[type="text"]', context).focus(function() {
+      Drupal.settings.tokenFocusedField = this;
+    });
+
+    $('.token-click-insert .token-key', context).once('token-click-insert', function() {
+      var newThis = $('<a href="javascript:void(0);" title="' + Drupal.t('Insert this token into your form') + '">' + $(this).html() + '</a>').click(function(){
+        if (typeof Drupal.settings.tokenFocusedField == 'undefined') {
+          alert(Drupal.t('First click a text field to insert your tokens into.'));
+        }
+        else {
+          var myField = Drupal.settings.tokenFocusedField;
+          var myValue = $(this).text();
+
+          //IE support
+          if (document.selection) {
+            myField.focus();
+            sel = document.selection.createRange();
+            sel.text = myValue;
+          }
+
+          //MOZILLA/NETSCAPE support
+          else if (myField.selectionStart || myField.selectionStart == '0') {
+            var startPos = myField.selectionStart;
+            var endPos = myField.selectionEnd;
+            myField.value = myField.value.substring(0, startPos)
+                          + myValue
+                          + myField.value.substring(endPos, myField.value.length);
+          } else {
+            myField.value += myValue;
+          }
+
+          $('html,body').animate({scrollTop: $(myField).offset().top}, 500);
+        }
+        return false;
+      });
+      $(this).html(newThis);
+    });
+  }
+};
+
+})(jQuery);
diff --git a/core/modules/token/token.module b/core/modules/token/token.module
new file mode 100644
index 0000000..2b9b574
--- /dev/null
+++ b/core/modules/token/token.module
@@ -0,0 +1,411 @@
+<?php
+
+/**
+ * @file
+ * Provides a user interface for tokens.
+ */
+
+/**
+ * The maximum depth for token tree recursion.
+ */
+define('TOKEN_MAX_DEPTH', 9);
+
+/**
+ * Implements hook_help().
+ */
+function token_help($path, $arg) {
+  switch ($path) {
+    case 'admin/help#token':
+      $output = '';
+      $output .= '<h3>' . t('About') . '</h3>';
+      $output .= '<p>' . t('The Token module provides a token browser, which allows you to select tokens to insert into fields that accept tokens.') . '</p>';
+    return $output;
+  }
+}
+
+/**
+ * Implements hook_menu().
+ */
+function token_menu() {
+  $items['token/autocomplete/%token_type'] = array(
+    'page callback' => 'token_autocomplete_token',
+    'page arguments' => array(2),
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+    'file' => 'token.pages.inc',
+  );
+
+  $items['token/tree'] = array(
+    'page callback' => 'token_page_output_tree',
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+    'file' => 'token.pages.inc',
+  );
+
+  return $items;
+}
+
+/**
+ * Loads a token type for menu router items.
+ *
+ * @param string $token_type
+ *   String representing the type of token to load (generally the module or
+ *   entity name).
+ *
+ * @return array
+ *   Array containing the token information for this token type.
+ */
+function token_type_load($token_type) {
+  $info = token_get_info();
+  return isset($info['types'][$token_type]) ? $info['types'][$token_type] : FALSE;
+}
+
+/**
+ * Implements hook_theme().
+ */
+function token_theme() {
+  // Displays a tree of available tokens.
+  $info['token_tree'] = array(
+    'variables' => array(
+      // Array of token types to list.
+      'token_types' => array(),
+      // TRUE to also include global token types.
+      'global_types' => TRUE,
+      // TRUE to have "click to insert" functionality.
+      'click_insert' => TRUE,
+      // TRUE to show restricted tokens.
+      'show_restricted' => FALSE,
+      // Depth limit for tree.
+      'recursion_limit' => 3,
+      // TRUE to display as a flat list; FALSE as nested list.
+      'flat' => FALSE,
+    ),
+    'file' => 'token.pages.inc',
+  );
+
+  // Link to pop up a token tree in a dialog or new window.
+  $info['token_tree_link'] = array(
+    'variables' => array(
+      // Link text: defaults to t('Browse available tokens').
+      'text' => NULL,
+      // Options for the URL.
+      'options' => array(),
+      // TRUE to display using a JavaScript dialog; FALSE as new window.
+      'dialog' => TRUE,
+    ),
+    'file' => 'token.pages.inc',
+  );
+
+  return $info;
+}
+
+/**
+ * Implements hook_library_info().
+ */
+function token_library_info() {
+  $libraries['token_dialog'] = array(
+    'title' => 'Token dialog',
+    'version' => '1.0',
+    'js' => array(
+      drupal_get_path('module', 'token') . '/token.js' => array(),
+    ),
+    'dependencies' => array(
+      array('system', 'jquery.ui.dialog'),
+      array('system', 'drupal'),
+    ),
+  );
+
+  return $libraries;
+}
+
+/**
+ * Retrieves and sorts token information.
+ *
+ * @param string $token_type
+ *   (optional) A particular token type to return information for.
+ * @param string $token
+ *   (optional) A particular token name within $token_type to return
+ *   information for.
+ *
+ * @return array
+ *   An array of all token information from hook_token_info(), or the array
+ *   of a token type or specific token.
+ *
+ * @see hook_token_info()
+ * @see hook_token_info_alter()
+ */
+function token_get_info($token_type = NULL, $token = NULL) {
+  global $language;
+
+  // @todo This function needs to use database caching.
+
+  // Use the advanced drupal_static() pattern, since this is called very often.
+  static $drupal_static_fast;
+  if (!isset($drupal_static_fast)) {
+    $drupal_static_fast['token_info'] = &drupal_static(__FUNCTION__);
+  }
+  $token_info = &$drupal_static_fast['token_info'];
+
+  if (empty($token_info)) {
+    $token_info = token_info();
+
+    foreach (array_keys($token_info['types']) as $type_key) {
+      if (isset($token_info['types'][$type_key]['type'])) {
+        $base_type = $token_info['types'][$type_key]['type'];
+        // If this token type extends another token type, then merge in
+        // the base token type's tokens.
+        if (isset($token_info['tokens'][$base_type])) {
+          $token_info['tokens'] += array($type_key => array());
+          $token_info['tokens'][$type_key] += $token_info['tokens'][$base_type];
+        }
+      }
+      else {
+        // Add a 'type' value to each token type so we can properly use
+        // token_type_load().
+        $token_info['types'][$type_key]['type'] = $type_key;
+      }
+    }
+
+    // Pre-sort tokens.
+    uasort($token_info['types'], 'token_asort_tokens');
+    foreach (array_keys($token_info['tokens']) as $type) {
+      uasort($token_info['tokens'][$type], 'token_asort_tokens');
+    }
+  }
+
+  if (isset($token_type) && isset($token)) {
+    return isset($token_info['tokens'][$token_type][$token]) ? $token_info['tokens'][$token_type][$token] : NULL;
+  }
+  elseif (isset($token_type)) {
+    return isset($token_info['types'][$token_type]) ? $token_info['types'][$token_type] : NULL;
+  }
+  else {
+    return $token_info;
+  }
+}
+
+/**
+ * Sorts tokens by the 'name' property.
+ *
+ * Callback for uasort() within token_get_info().
+ */
+function token_asort_tokens($token_a, $token_b) {
+  return strnatcmp($token_a['name'], $token_b['name']);
+}
+
+/**
+ * Gets a list of token types that can be used without any context (global).
+ *
+ * @return
+ *   An array of global token types.
+ */
+function token_get_global_token_types() {
+  $global_types = &drupal_static(__FUNCTION__, array());
+
+  // @todo This function needs to use database caching.
+
+  if (empty($global_types)) {
+    $token_info = token_get_info();
+    foreach ($token_info['types'] as $type => $type_info) {
+      // If the token types has not specified that 'needs-data' => TRUE, then
+      // it is a global token type that will always be replaced in any context.
+      if (empty($type_info['needs-data'])) {
+        $global_types[] = $type;
+      }
+    }
+  }
+
+  return $global_types;
+}
+
+/**
+ * Builds a tree array of tokens used for theming or information.
+ *
+ * @param string $token_type
+ *   The token type.
+ * @param array $options
+ *   Array containing options governing how the tree is built:
+ *   - restricted: TRUE to show restricted tokens; FALSE (default) to hide them.
+ *   - depth: Maximum depth to traverse in the token tree.
+ *   - flat: TRUE to return a flat array; FALSE (default) to return a tree.
+ *
+ * @return array
+ *    Array containing the tree of tokens.
+ */
+function token_build_tree($token_type, array $options = array()) {
+  global $language;
+
+  // Static cache of already built token trees.
+  $trees = &drupal_static(__FUNCTION__, array());
+
+  // @todo This function needs to use database caching.
+
+  $options += array(
+    'restricted' => FALSE,
+    'depth' => 4,
+    'data' => array(),
+    'values' => FALSE,
+    'flat' => FALSE,
+  );
+
+  // Do not allow past the maximum token information depth.
+  $options['depth'] = min($options['depth'], TOKEN_MAX_DEPTH);
+
+  // If $token_type is an entity, make sure we are using the actual token type.
+  if ($entity_token_type = token_get_entity_mapping('entity', $token_type)) {
+    $token_type = $entity_token_type;
+  }
+
+  $tree_cid = "tree:{$token_type}:{$language->language}:{$options['depth']}";
+
+  // If we do not have this base tree in the static cache, check {cache_token}
+  // otherwise generate and store it in the cache.
+  if (!isset($trees[$tree_cid])) {
+    $options['parents'] = array();
+    $trees[$tree_cid] = _token_build_tree($token_type, $options);
+  }
+
+  $tree = $trees[$tree_cid];
+
+  // If the user has requested a flat tree, convert it.
+  if (!empty($options['flat'])) {
+    $tree = token_flatten_tree($tree);
+  }
+
+  // Fill in token values.
+  if (!empty($options['values'])) {
+    $token_values = array();
+    foreach ($tree as $token => $token_info) {
+      if (!empty($token_info['dynamic']) || !empty($token_info['restricted'])) {
+        continue;
+      }
+      elseif (!isset($token_info['value'])) {
+        $token_values[$token_info['token']] = $token;
+      }
+    }
+    if (!empty($token_values)) {
+      $token_values = token_generate($token_type, $token_values, $options['data']);
+      foreach ($token_values as $token => $replacement) {
+        $tree[$token]['value'] = $replacement;
+      }
+    }
+  }
+
+  return $tree;
+}
+
+/**
+ * Flattens a token tree.
+ *
+ * @param array $tree
+ *   The tree to flatten.
+ *
+ * @return array
+ *   The flattened tree.
+ */
+function token_flatten_tree($tree) {
+  $result = array();
+  foreach ($tree as $token => $token_info) {
+    $result[$token] = $token_info;
+    if (isset($token_info['children']) && is_array($token_info['children'])) {
+      $result += token_flatten_tree($token_info['children']);
+    }
+  }
+  return $result;
+}
+
+/**
+ * Generates a token tree.
+ */
+function _token_build_tree($token_type, array $options) {
+  $options += array(
+    'parents' => array(),
+  );
+
+  $info = token_get_info();
+  if ($options['depth'] <= 0 || !isset($info['types'][$token_type]) || !isset($info['tokens'][$token_type])) {
+    return array();
+  }
+
+  $tree = array();
+  foreach ($info['tokens'][$token_type] as $token => $token_info) {
+    // Build the raw token string.
+    $token_parents = $options['parents'];
+    if (empty($token_parents)) {
+      // If the parents array is currently empty, assume the token type is its
+      // parent.
+      $token_parents[] = $token_type;
+    }
+    elseif (in_array($token, array_slice($token_parents, 1))) {
+      // Prevent duplicate recursive tokens. For example, this will prevent
+      // the tree from generating the following tokens or deeper:
+      // [comment:parent:parent]
+      // [comment:parent:root:parent]
+      continue;
+    }
+
+    $token_parents[] = $token;
+    if (!empty($token_info['dynamic'])) {
+      $token_parents[] = '?';
+    }
+    $raw_token = '[' . implode(':', $token_parents) . ']';
+    $tree[$raw_token] = $token_info;
+    $tree[$raw_token]['raw token'] = $raw_token;
+
+    // Add the token's real name (leave out the base token type).
+    $tree[$raw_token]['token'] = implode(':', array_slice($token_parents, 1));
+
+    // Add the token's parent as its raw token value.
+    if (!empty($options['parents'])) {
+      $tree[$raw_token]['parent'] = '[' . implode(':', $options['parents']) . ']';
+    }
+
+    // Fetch the child tokens.
+    if (!empty($token_info['type'])) {
+      $child_options = $options;
+      $child_options['depth']--;
+      $child_options['parents'] = $token_parents;
+      $tree[$raw_token]['children'] = _token_build_tree($token_info['type'], $child_options);
+    }
+  }
+
+  return $tree;
+}
+
+/**
+ * Returns all entity type to token type mappings, or looks up one mapping.
+ *
+ * This is needed especially for taxonomy-related tokens, which do not
+ * use the entity name as the token type name.
+ *
+ * @param string $lookup_type
+ *   (optional) Type of value to search for, either 'token' or 'entity'.
+ * @param string $lookup_value
+ *   (optional) Value to search for (a token type or entity string). For
+ *   instance, if $lookup_type is 'token', $lookup_value gives a token type
+ *   that you want to find the entity type for.
+ *
+ * @return array|string|null
+ *   Array of all mappings of entity type to the corresponding token type, or
+ *   if both $lookup_type and $lookup_value are provided, the corresponding
+ *   mapping (or NULL if not found).
+ */
+function token_get_entity_mapping($lookup_type = 'token', $lookup_value = NULL) {
+  $mapping = &drupal_static(__FUNCTION__, array());
+
+  if (empty($mapping)) {
+    foreach (entity_get_info() as $entity_type => $info) {
+      $mapping[$entity_type] = !empty($info['token type']) ? $info['token type'] : $entity_type;
+    }
+  }
+
+  if (!isset($lookup_value)) {
+    return $mapping;
+  }
+  elseif ($lookup_type == 'token') {
+    return array_search($lookup_value, $mapping);
+  }
+  elseif ($lookup_type == 'entity') {
+    return isset($mapping[$value]) ? $mapping[$value] : FALSE;
+  }
+}
diff --git a/core/modules/token/token.pages.inc b/core/modules/token/token.pages.inc
new file mode 100644
index 0000000..fbe009d
--- /dev/null
+++ b/core/modules/token/token.pages.inc
@@ -0,0 +1,254 @@
+<?php
+
+/**
+ * @file
+ * User page callbacks for the token module.
+ */
+
+/**
+ * Returns HTML for a link to a token tree or token tree dialog.
+ *
+ * @ingroup themeable
+ */
+function theme_token_tree_link($variables) {
+  if (empty($variables['text'])) {
+    $variables['text'] = t('Browse available tokens.');
+  }
+
+  if (!empty($variables['dialog'])) {
+    drupal_add_library('token', 'token_dialog');
+    $variables['options']['attributes']['class'][] = 'token-dialog';
+  }
+
+  $info = token_theme();
+  $variables['options']['query']['options'] = array_intersect_key($variables, $info['token_tree']['variables']);
+
+  // We should never pass the dialog option to theme_token_tree(). It is only
+  // used for this function.
+  unset($variables['options']['query']['options']['dialog']);
+
+  // Add a security token so that the tree page should only work when used
+  // when the dialog link is output with theme('token_tree_link').
+  $variables['options']['query']['token'] = drupal_get_token('token-tree:' . serialize($variables['options']['query']['options']));
+
+  // Because PHP converts query strings with arrays into a different syntax on
+  // the next request, the options have to be encoded with JSON in the query
+  // string so that we can reliably decode it for token comparison.
+  $variables['options']['query']['options'] = drupal_json_encode($variables['options']['query']['options']);
+
+  // Set the token tree to open in a separate window.
+  if (!isset($variables['options']['attributes'])) {
+    $variables['options']['attributes'] = array();
+  }
+  $variables['options']['attributes'] += array('target' => '_blank');
+
+  return l($variables['text'], 'token/tree', $variables['options']);
+}
+
+/**
+ * Page callback: Outputs a token tree on its own page.
+ *
+ * @see token_menu()
+ */
+function token_page_output_tree() {
+  $options = isset($_GET['options']) ? drupal_json_decode($_GET['options']) : array();
+
+  // Check the token against the serialized options to prevent random access to
+  // the token browser page.
+  if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], 'token-tree:' . serialize($options))) {
+    return MENU_ACCESS_DENIED;
+  }
+
+  $output = theme('token_tree', $options);
+  print '<html><head><title></title>' . drupal_get_css() . drupal_get_js() . '</head>';
+  print '<body class="token-tree">' . $output . '</body></html>';
+  drupal_exit();
+}
+
+/**
+ * Returns HTML for a tree display of nested tokens.
+ *
+ * @ingroup themeable
+ */
+function theme_token_tree($variables) {
+  $token_types = $variables['token_types'];
+
+  if ($token_types == 'all') {
+    $token_types = array_keys($info['types']);
+  }
+  elseif ($variables['global_types']) {
+    $token_types = array_merge($token_types, token_get_global_token_types());
+  }
+
+  $options = array(
+    'flat' => $variables['flat'],
+    'restricted' => $variables['show_restricted'],
+    'depth' => $variables['recursion_limit'],
+  );
+
+  // Make an array suitable for theme_item_list(), containing the nested
+  // list of tokens.
+
+  $info = token_get_info();
+  $items = array();
+
+  $items['token_list_title'] = array(
+    '#markup' => t('List of available tokens'),
+  );
+
+  foreach ($info['types'] as $type => $type_info) {
+    if (!in_array($type, $token_types)) {
+      continue;
+    }
+
+    $items[$type] = array(
+      '#type' => 'details',
+      '#title' => $type_info['name'],
+      '#description' => $type_info['description'],
+      '#collapsed' => TRUE,
+    );
+
+    $tree = token_build_tree($type, $options);
+    $items[$type]['token_list_tree'] = _token_make_list($tree, $variables['show_restricted']);
+  }
+
+  return drupal_render($items);
+}
+
+/**
+ * Recursively builds an item list of tokens.
+ */
+function _token_make_list($tree, $show_restricted) {
+
+  $items = array();
+
+  foreach ($tree as $token => $token_info) {
+    // Skip restricted tokens.
+    if (!empty($token_info['restricted']) && !$show_restricted) {
+      continue;
+    }
+
+    // Render each item as either a plain bit of markup or a details
+    // element, depending on if it has children or not.
+
+    if (isset($token_info['children'])) {
+      $items[$token] = array(
+        '#type' => 'details',
+        '#title' => $token_info['name'],
+        '#description' => $token_info['description'],
+        '#collapsed' => TRUE,
+      );
+
+      $items[$token]['token_list_tree'] = _token_make_list($token_info['children'], $show_restricted);
+    }
+    else {
+      $items[$token] = array(
+        // @todo: This should be formatted better eventually to look like a
+        // table, and to have links to insert the token.
+        '#markup' => '<p>' . $token . ' - ' . $token_info['name'] . ': ' . $token_info['description'] . '</p>',
+      );
+    }
+  }
+
+  return $items;
+}
+
+/**
+ * Page callback: Returns auto-complete options for tokens.
+ */
+function token_autocomplete() {
+  $args = func_get_args();
+  $string = implode('/', $args);
+
+  $token_info = token_info();
+
+  preg_match_all('/\[([^\s\]:]*):?([^\s\]]*)?\]?/', $string, $matches);
+  $types = $matches[1];
+  $tokens = $matches[2];
+
+  foreach ($types as $index => $type) {
+    if (!empty($tokens[$index]) || isset($token_info['types'][$type])) {
+      token_autocomplete_token($type, $tokens[$index]);
+    }
+    else {
+      token_autocomplete_type($type);
+    }
+  }
+
+}
+
+/**
+ * Outputs the best token type matches for the given string in JSON format.
+ *
+ * There is no return value (output is sent to the browser). The output is
+ * an array of the closest matches between the input string and existing
+ * token types, in JSON format.
+ *
+ * @param string $string
+ *   String to match.
+ */
+function token_autocomplete_type($string = '') {
+  $token_info = token_info();
+  $types = $token_info['types'];
+  $matches = array();
+
+  foreach ($types as $type => $info) {
+    if (!$string || strpos($type, $string) === 0) {
+      $type_key = "[{$type}:";
+      $matches[$type_key] = levenshtein($type, $string);
+    }
+  }
+
+  if ($string) {
+    asort($matches);
+  }
+  else {
+    ksort($matches);
+  }
+
+  $matches = drupal_map_assoc(array_keys($matches));
+  drupal_json_output($matches);
+}
+
+/**
+ * Outputs the best token type matches for the given string in JSON format.
+ *
+ * There is no return value (output is sent to the browser). The output is
+ * an array of the closest matches between the input string and existing
+ * tokens, in JSON format.
+ *
+ * @param array $token_type
+ *   The type of token (array of token information) to look for matches on.
+ * @param string ...
+ *   The rest of the arguments are used for matching.
+ */
+function token_autocomplete_token($token_type) {
+  $args = func_get_args();
+  array_shift($args);
+  $string = trim(implode('/', $args));
+  $string = substr($string, strrpos($string, '['));
+
+  $token_type = $token_type['type'];
+  $matches = array();
+
+  if (!drupal_strlen($string)) {
+    $matches["[{$token_type}:"] = 0;
+  }
+  else {
+    $depth = max(1, substr_count($string, ':'));
+    $tree = token_build_tree($token_type, array('flat' => TRUE, 'depth' => $depth));
+    foreach (array_keys($tree) as $token) {
+      if (strpos($token, $string) === 0) {
+        $matches[$token] = levenshtein($token, $string);
+        if (isset($tree[$token]['children'])) {
+          $token = rtrim($token, ':]') . ':';
+          $matches[$token] = levenshtein($token, $string);
+        }
+      }
+    }
+  }
+
+  asort($matches);
+  $matches = drupal_map_assoc(array_keys($matches));
+  drupal_json_output($matches);
+}
diff --git a/core/modules/user/user.admin.inc b/core/modules/user/user.admin.inc
index 263add5..e3803f0 100644
--- a/core/modules/user/user.admin.inc
+++ b/core/modules/user/user.admin.inc
@@ -385,8 +385,19 @@ function user_admin_settings($form, &$form_state) {
     '#type' => 'vertical_tabs',
   );
   // These email tokens are shared for all settings, so just define
-  // the list once to help ensure they stay in sync.
-  $email_token_help = t('Available variables are: [site:name], [site:url], [user:name], [user:mail], [site:login-url], [site:url-brief], [user:edit-url], [user:one-time-login-url], [user:cancel-url].');
+  // the list or link once.
+  if (module_exists('token')) {
+    $email_token_help = theme('token_tree_link', array(
+      'text' => t('Browse tokens that can be used in e-mail messages'),
+      'token_types' => array('user'),
+      'show_restricted' => TRUE,
+      // Uncomment this line to open the browser in a real window.
+      // 'dialog' => FALSE,
+    ));
+  }
+  else {
+    $email_token_help = t('Available variables are: [site:name], [site:url], [user:name], [user:mail], [site:login-url], [site:url-brief], [user:edit-url], [user:one-time-login-url], [user:cancel-url].');
+  }
 
   $form['email_admin_created'] = array(
     '#type' => 'details',
