diff --git token.module token.module
index bb2e98a..afd9f02 100644
--- token.module
+++ token.module
@@ -770,6 +770,23 @@ function _token_build_tree($token_type, array $options) {
   return $tree;
 }
 
+function token_render_array($array, array $options = array()) {
+  $rendered = array_map('render', $array);
+  if (!empty($options['sanitize'])) {
+    $rendered = array_map('check_plain', $rendered);
+  }
+  $join = isset($options['join']) ? $options['join'] : ', ';
+  return implode($join, $rendered);
+}
+
+function token_render_array_value($value, array $options = array()) {
+  $rendered = render($value);
+  if (!empty($options['sanitize'])) {
+    $rendered = check_plain($rendered);
+  }
+  return $rendered;
+}
+
 /**
  * Find tokens that have been declared twice by different modules.
  */
diff --git token.test token.test
index 7b8f851..eecee87 100644
--- token.test
+++ token.test
@@ -739,3 +739,36 @@ class TokenCurrentPageTestCase extends TokenTestHelper {
     $this->assertPageTokens("node/{$node->nid}", $tokens);
   }
 }
+
+class TokenArrayTestCase extends TokenTestHelper {
+  public static function getInfo() {
+    return array(
+      'name' => 'Array token tests',
+      'description' => 'Test the array tokens.',
+      'group' => 'Token',
+    );
+  }
+
+  function testArrayTokens() {
+    $array = array(0 => 'a', 1 => 'b', 2 => 'c', 4 => 'd');
+
+    // Test the profile token values.
+    $tokens = array(
+      'first' => 'a',
+      'last' => 'd',
+      'value:0' => 'a',
+      'value:2' => 'c',
+      'count' => 4,
+      'keys' => '0, 1, 2, 4',
+      'keys:value:3' => '4',
+      'keys:join' => '0124',
+      'reversed' => 'd, c, b, a',
+      'reversed:keys' => '4, 2, 1, 0',
+      'join:/' => 'a/b/c/d',
+      'join' => 'abcd',
+      'join:, ' => 'a, b, c, d',
+      'join: ' => 'a b c d',
+    );
+    $this->assertTokens('array', $array, $tokens);
+  }
+}
diff --git token.tokens.inc token.tokens.inc
index 89fe51a..f4458b5 100644
--- token.tokens.inc
+++ token.tokens.inc
@@ -149,6 +149,11 @@ function token_token_info() {
       'type' => 'file',
     );
   }
+  $info['tokens']['user']['roles'] = array(
+    'name' => t('Roles'),
+    'description' => t('The user roles associated with the user account.'),
+    'type' => 'array',
+  );
 
   // Current user tokens.
   $info['tokens']['current-user']['ip-address'] = array(
@@ -210,6 +215,7 @@ function token_token_info() {
     'dynamic' => TRUE,
   );
 
+  // URL tokens.
   $info['types']['url'] = array(
     'name' => t('URL'),
     'description' => t('Tokens related to URLs.'),
@@ -232,6 +238,40 @@ function token_token_info() {
     'description' => t('The absolute URL.'),
   );
 
+  // Array tokens.
+  $info['types']['array'] = array(
+    'name' => t('Array'),
+    'description' => t('Tokens related to arrays of strings.'),
+    'needs-data' => 'array',
+  );
+  $info['tokens']['array']['first'] = array(
+    'name' => t('First'),
+    'description' => t('The first element of the array.'),
+  );
+  $info['tokens']['array']['last'] = array(
+    'name' => t('Last'),
+    'description' => t('The last element of the array.'),
+  );
+  $info['tokens']['array']['count'] = array(
+    'name' => t('Count'),
+    'description' => t('The number of elements in the array.'),
+  );
+  $info['tokens']['array']['reversed'] = array(
+    'name' => t('Reversed'),
+    'description' => t('The array reversed.'),
+    'type' => 'array',
+  );
+  $info['tokens']['array']['keys'] = array(
+    'name' => t('Keys'),
+    'description' => t('The array of keys of the array.'),
+    'type' => 'array',
+  );
+  $info['tokens']['array']['join'] = array(
+    'name' => t('Imploded'),
+    'description' => t('The values of the array joined together with a custom string in-between each value.'),
+    'dynamic' => TRUE,
+  );
+
   return $info;
 }
 
@@ -392,6 +432,9 @@ function token_tokens($type, $tokens, array $data = array(), array $options = ar
             $replacements[$original] = theme('user_picture', array('account' => $account));
           }
           break;
+        case 'roles':
+          $replacements[$original] = token_render_array($account->roles, $options);
+          break;
       }
     }
 
@@ -401,9 +444,12 @@ function token_tokens($type, $tokens, array $data = array(), array $options = ar
       $account->picture->description = '';
       $replacements += token_generate('file', $picture_tokens, array('file' => $account->picture), $options);
     }
-    if (($url_tokens = token_find_with_prefix($tokens, 'url'))) {
+    if ($url_tokens = token_find_with_prefix($tokens, 'url')) {
       $replacements += token_generate('url', $url_tokens, entity_uri('user', $account), $options);
     }
+    if ($role_tokens = token_find_with_prefix($tokens, 'roles')) {
+      $replacements += token_generate('array', $role_tokens, array('array' => $account->roles), $options);
+    }
   }
 
   // Current user tokens.
@@ -555,6 +601,64 @@ function token_tokens($type, $tokens, array $data = array(), array $options = ar
     }
   }
 
+  // Array tokens.
+  if ($type == 'array' && !empty($data['array']) && is_array($data['array'])) {
+    $array = $data['array'];
+
+    foreach ($tokens as $name => $original) {
+      switch ($name) {
+        case 'first':
+          $first = reset($array);
+          $replacements[$original] = token_render_array_value($first, $options);
+          reset($array);
+          break;
+        case 'last':
+          $last = end($array);
+          $replacements[$original] = token_render_array_value($last, $options);
+          reset($array);
+          break;
+        case 'count':
+          $replacements[$original] = count($array);
+          break;
+        case 'keys':
+          $keys = array_keys($array);
+          $replacements[$original] = token_render_array($keys, $options);
+          break;
+        case 'reversed':
+          $reversed = array_reverse($array, TRUE);
+          $replacements[$original] = token_render_array($reversed, $options);
+          break;
+        case 'join':
+          $replacements[$original] = token_render_array($array, array('join' => '') + $options);
+          break;
+      }
+    }
+
+    // Dynamic tokens.
+    if ($value_tokens = token_find_with_prefix($tokens, 'value')) {
+      foreach ($value_tokens as $key => $original) {
+        if (isset($array[$key])) {
+          $replacements[$original] = token_render_array_value($array[$key], $options);
+        }
+      }
+    }
+    if ($join_tokens = token_find_with_prefix($tokens, 'join')) {
+      foreach ($join_tokens as $join => $original) {
+        $replacements[$original] = token_render_array($array, array('join' => $join) + $options);
+      }
+    }
+
+    // Chained token relationships.
+    if ($key_tokens = token_find_with_prefix($tokens, 'keys')) {
+      $replacements += token_generate('array', $key_tokens, array('array' => array_keys($array)), $options);
+    }
+    if ($reversed_tokens = token_find_with_prefix($tokens, 'reversed')) {
+      $replacements += token_generate('array', $reversed_tokens, array('array' => array_reverse($array, TRUE)), $options);
+    }
+
+    // @todo Handle if the array values are not strings and could be chained.
+  }
+
   // If $type is a token type, $data[$type] is empty but $data[$entity_type] is
   // not, re-run token replacements.
   if (empty($data[$type]) && ($entity_type = token_get_entity_mapping('token', $type)) && $entity_type != $type && !empty($data[$entity_type]) && empty($options['recursive'])) {
