diff --git a/THEMING.txt b/THEMING.txt
index f0001b8..5ea699f 100644
--- a/THEMING.txt
+++ b/THEMING.txt
@@ -111,5 +111,5 @@
 use webform-submission-[node id here].tpl.php for individual nodes if desired.
 Note that the contents of this template are used not only for display of
 submissions in the Webform interface but also in e-mails when printing out
-the %email_values token.
+the [webform-submission:values] token.
 
diff --git a/components/email.inc b/components/email.inc
index 9a65e7f..4efaef2 100644
--- a/components/email.inc
+++ b/components/email.inc
@@ -56,15 +56,15 @@
     '#size' => 60,
     '#maxlength' => 127,
     '#weight' => 0,
-    '#attributes' => ($component['value'] == '%useremail' && count(form_get_errors()) == 0) ? array('disabled' => TRUE) : array(),
+    '#attributes' => ($component['value'] == '[current-user:mail]' && count(form_get_errors()) == 0) ? array('disabled' => TRUE) : array(),
     '#id' => 'email-value',
   );
   $form['user_email'] = array(
     '#type' => 'checkbox',
     '#title' => t('User email as default'),
-    '#default_value' => $component['value'] == '%useremail' ? 1 : 0,
+    '#default_value' => $component['value'] == '[current-user:mail]' ? 1 : 0,
     '#description' => t('Set the default value of this field to the user email, if he/she is logged in.'),
-    '#attributes' => array('onclick' => 'getElementById("email-value").value = (this.checked ? "%useremail" : ""); getElementById("email-value").disabled = this.checked;'),
+    '#attributes' => array('onclick' => 'getElementById("email-value").value = (this.checked ? "[current-user:mail]" : ""); getElementById("email-value").disabled = this.checked;'),
     '#weight' => 0,
     '#element_validate' => array('_webform_edit_email_validate'),
   );
@@ -103,7 +103,7 @@
  */
 function _webform_edit_email_validate($element, &$form_state) {
   if ($form_state['values']['user_email']) {
-    $form_state['values']['value'] = '%useremail';
+    $form_state['values']['value'] = '[current-user:mail]';
   }
 }
 
diff --git a/components/hidden.inc b/components/hidden.inc
index e173c29..99e3210 100644
--- a/components/hidden.inc
+++ b/components/hidden.inc
@@ -73,8 +73,7 @@
   // Set filtering options for "value" types, which are not displayed to the
   // end user so they do not need to be sanitized.
   $strict = $component['extra']['hidden_type'] != 'value';
-  $allow_anonymous = $component['extra']['hidden_type'] == 'value';
-  $default_value = $filter ? _webform_filter_values($component['value'], $node, NULL, NULL, $strict, $allow_anonymous) : $component['value'];
+  $default_value = $filter ? _webform_filter_values($component['value'], $node, NULL, NULL, $strict) : $component['value'];
   if (isset($value[0])) {
     $default_value = $value[0];
   }
diff --git a/includes/webform.admin.inc b/includes/webform.admin.inc
index f075987..a3be128 100644
--- a/includes/webform.admin.inc
+++ b/includes/webform.admin.inc
@@ -51,21 +51,21 @@
   $form['email']['webform_default_from_address']  = array(
     '#type' => 'textfield',
     '#title' => t('From address'),
-    '#default_value' => variable_get('webform_default_from_address', variable_get('site_mail', ini_get('sendmail_from'))),
+    '#default_value' => webform_variable_get('webform_default_from_address'),
     '#description' => t('The default sender address for emailed webform results; often the e-mail address of the maintainer of your forms.'),
   );
 
   $form['email']['webform_default_from_name']  = array(
     '#type' => 'textfield',
     '#title' => t('From name'),
-    '#default_value' => variable_get('webform_default_from_name', variable_get('site_name', '')),
+    '#default_value' => webform_variable_get('webform_default_from_name'),
     '#description' => t('The default sender name which is used along with the default from address.'),
   );
 
   $form['email']['webform_default_subject']  = array(
     '#type' => 'textfield',
     '#title' => t('Default subject'),
-    '#default_value' => variable_get('webform_default_subject', t('Form submission from: %title')),
+    '#default_value' => webform_variable_get('webform_default_subject'),
     '#description' => t('The default subject line of any e-mailed results.'),
   );
 
diff --git a/includes/webform.emails.inc b/includes/webform.emails.inc
index cfd7cb5..7ab54d1 100644
--- a/includes/webform.emails.inc
+++ b/includes/webform.emails.inc
@@ -284,7 +284,7 @@
   );
 
   $form['template']['tokens'] = array(
-    '#markup' => theme('webform_token_help', array('groups' => 'all')),
+    '#markup' => theme('webform_token_help', array('groups' => array('node', 'webform-submission'))),
   );
 
   $form['template']['components'] = array(
@@ -294,7 +294,7 @@
     '#default_value' => array_diff(array_keys($node->webform['components']), $email['excluded_components']),
     '#multiple' => TRUE,
     '#size' => 10,
-    '#description' => t('The selected components will be included in the %email_values token. Individual values may still be printed if explicitly specified as a %email[key] in the template.'),
+    '#description' => t('The selected components will be included in the [webform-submission:values] token. Individual values may still be printed if explicitly specified as a [webform-submision:values:?] in the template.'),
     '#process' => array('webform_component_select'),
   );
 
diff --git a/includes/webform.pages.inc b/includes/webform.pages.inc
index 97c9395..1c0aabf 100644
--- a/includes/webform.pages.inc
+++ b/includes/webform.pages.inc
@@ -64,7 +64,7 @@
     '#type' => 'item',
     '#title' => t('Redirection location'),
     '#theme' => 'webform_advanced_redirection_form',
-    '#description' => t('Choose where to redirect the user upon successful submission.') . ' ' . t('The <em>Custom URL</em> option supports Webform token replacements.') . theme('webform_token_help', array('groups' => array('basic', 'node', 'special', 'submission'))),
+    '#description' => t('Choose where to redirect the user upon successful submission.') . ' ' . t('The <em>Custom URL</em> option supports Webform token replacements.') . theme('webform_token_help', array('groups' => array('node', 'webform-submission'))),
   );
   $form['submission']['redirection']['redirect']= array(
     '#type' => 'radios',
diff --git a/includes/webform.submissions.inc b/includes/webform.submissions.inc
index 925aff5..6e8e22a 100644
--- a/includes/webform.submissions.inc
+++ b/includes/webform.submissions.inc
@@ -196,7 +196,7 @@
     }
 
     // Replace tokens in the message.
-    $email['message'] = _webform_filter_values($email['message'], $node, $submission, $email, FALSE, TRUE);
+    $email['message'] = _webform_filter_values($email['message'], $node, $submission, $email, FALSE);
 
     // Build the e-mail headers.
     $email['headers'] = theme(array('webform_mail_headers_' . $node->nid, 'webform_mail_headers'), array('node' => $node, 'submission' => $submission, 'email' => $email));
diff --git a/templates/webform-mail.tpl.php b/templates/webform-mail.tpl.php
index 401f2b2..f898a8f 100644
--- a/templates/webform-mail.tpl.php
+++ b/templates/webform-mail.tpl.php
@@ -19,18 +19,18 @@
  * when using the "default" e-mail template.
  */
 ?>
-<?php print ($email['html'] ? '<p>' : '') . t('Submitted on %date'). ($email['html'] ? '</p>' : ''); ?>
+<?php print ($email['html'] ? '<p>' : '') . t('Submitted on [webform-submission:date:long]'). ($email['html'] ? '</p>' : ''); ?>
 
 <?php if ($user->uid): ?>
-<?php print ($email['html'] ? '<p>' : '') . t('Submitted by user: %username') . ($email['html'] ? '</p>' : ''); ?>
+<?php print ($email['html'] ? '<p>' : '') . t('Submitted by user: [webform-submission:user:name]') . ($email['html'] ? '</p>' : ''); ?>
 <?php else: ?>
-<?php print ($email['html'] ? '<p>' : '') . t('Submitted by anonymous user: [%ip_address]') . ($email['html'] ? '</p>' : ''); ?>
+<?php print ($email['html'] ? '<p>' : '') . t('Submitted by anonymous user: [webform-submission:ip-address]') . ($email['html'] ? '</p>' : ''); ?>
 <?php endif; ?>
 
 <?php print ($email['html'] ? '<p>' : '') . t('Submitted values are') . ':' . ($email['html'] ? '</p>' : ''); ?>
 
-%email_values
+[webform-submission:values]
 
 <?php print ($email['html'] ? '<p>' : '') . t('The results of this submission may be viewed at:') . ($email['html'] ? '</p>' : '') ?>
 
-<?php print ($email['html'] ? '<p>' : ''); ?>%submission_url<?php print ($email['html'] ? '</p>' : ''); ?>
+<?php print ($email['html'] ? '<p>' : ''); ?>[webform-submission:url]<?php print ($email['html'] ? '</p>' : ''); ?>
diff --git a/tests/submission.test b/tests/submission.test
index 84255a8..12e83f0 100644
--- a/tests/submission.test
+++ b/tests/submission.test
@@ -109,7 +109,8 @@
     $node = $this->testWebformForm();
     $submission_values = $value_type == 'sample' ? $this->testWebformPost() : array();
 
-    // Visit the node page with the "foo=bar" query, to test %get[] default values.
+    // Visit the node page with the "foo=bar" query, to test
+    // [current-page:query:?] default values.
     $this->drupalGet('node/' . $node->nid, array('query' => array('foo' => 'bar')));
     $this->assertText($node->body[LANGUAGE_NONE][0]['value'], t('Webform node created and accessible at !url', array('!url' => 'node/' . $node->nid)), t('Webform'));
 
diff --git a/tests/webform.test b/tests/webform.test
index fd20046..d0f3b03 100644
--- a/tests/webform.test
+++ b/tests/webform.test
@@ -17,7 +17,7 @@
     // Enable Webform.
     parent::setUp('webform', 'profile');
 
-    // Create a profile field to test %profile tokens.
+    // Create a profile field to test [user:?] tokens.
     db_query("INSERT INTO {profile_field} (title, name, explanation, category, type, weight, required, register, visibility, autocomplete, options, page) VALUES ('Gender', 'profile_gender', '', 'Profile', 'textfield', 0, 0, 0, 2, 0, '', '')");
 
     // Create a normal user that can view their own submissions.
@@ -396,7 +396,7 @@
           'form_key' => 'email',
           'name' => 'E-mail',
           'type' => 'email',
-          'value' => '%useremail',
+          'value' => '[current-user:mail]',
           'mandatory' => '0',
           'extra' => array(
             // SimpleTest does not support type="email" input fields.
@@ -449,7 +449,7 @@
           'form_key' => 'textfield_disabled',
           'name' => 'Textfield Disabled',
           'type' => 'textfield',
-          'value' => '%get[foo]',
+          'value' => '[current-page:query:foo]',
           'extra' => array(
             'disabled' => 1,
           ),
@@ -466,7 +466,7 @@
           'form_key' => 'textfield_profile',
           'name' => 'Textfield Profile',
           'type' => 'textfield',
-          'value' => '%profile[profile_gender]',
+          'value' => '[user:profile_gender]',
           'extra' => array(
             'width' => '20',
           ),
diff --git a/webform.install b/webform.install
index 19dacf4..ae989ee 100644
--- a/webform.install
+++ b/webform.install
@@ -229,7 +229,7 @@
         'not null' => FALSE,
       ),
       'excluded_components' => array(
-        'description' => 'A list of components that will not be included in the %email_values token. A list of CIDs separated by commas.',
+        'description' => 'A list of components that will not be included in the [webform-submission:values] token. A list of CIDs separated by commas.',
         'type' => 'text',
         'not null' => TRUE,
       ),
@@ -831,3 +831,161 @@
 function webform_update_7320() {
   db_query("UPDATE {file_managed} SET status = 1 WHERE fid IN (SELECT fid FROM {file_usage} WHERE module = :module_name)", array(':module_name' => 'webform'));
 }
+
+/**
+ * Rewrite token replacement system to use D7 tokens.
+ *
+ * If needed, please download and install the Token module from drupal.org.
+ * Otherwise some tokens will not be rendered. 
+ */
+function webform_update_7401(&$sandbox) {
+  // Define replacements.
+  $patterns = array(
+    '%username',
+    '%useremail',
+    '%uid',
+    '%date',
+    '%ip_address',
+    '%site',
+    '%nid',
+    '%title',
+    '%email_values',
+    '%submission_url',
+    '%sid',
+  );
+  $dpatterns = array(
+    '/%get\[([^\]]+)\]/m',
+    '/%email\[([^\]]+)\]\[([^\]]+)\]/m',
+    '/%email\[([^\]]+)\]/m',
+    '/%value\[([^\]]+)\]\[([^\]]+)\]/m',
+    '/%value\[([^\]]+)\]/m',
+    '/%profile\[([^\]]+)\]/m',
+  );
+  $replacements = array(
+    '[current-user:name]',
+    '[current-user:mail]',
+    '[current-user:uid]',
+    '[webform-submission:date:long]',
+    '[current-user:ip-address]',
+    '[site:name]',
+    '[node:nid]',
+    '[node:title]',
+    '[webform-submission:email-values]',
+    '[webform-submission:url]',
+    '[webform-submission:sid]',
+  );
+  $dreplacements = array(
+    '[current-page:query:$1]',
+    '[webform-submission:email:$2]',
+    '[webform-submission:email:$1]',
+    '[webform-submission:submission:$2]',
+    '[webform-submission:submission:$1]',
+    '[current-user:$1]',
+  );
+
+  // Set up the initial batch process.
+  if (!isset($sandbox['progress'])) {
+    $sandbox['progress'] = 0;
+    $sandbox['last_nid_processed'] = -1;
+    $sandbox['max'] = db_select('webform')
+      ->countQuery()
+      ->execute()
+      ->fetchField();
+
+    // Update tokens in variables.
+    $variables = array(
+      'webform_default_subject',
+      'webform_default_from_name',
+      'webform_default_from_address',
+    );
+    foreach ($variables as $variable) {
+      $value = variable_get($variable, NULL);
+      if ($value !== NULL) {
+        $value = str_replace($patterns, $replacements, $value);
+        $value = preg_replace($dpatterns, $dreplacements, $value);
+        variable_set($variable, $value);
+      }
+    }
+  }
+
+  $limit = variable_get('webform_update_batch_size', 100);
+  $webforms = db_select('webform', 'w')
+    ->fields('w')
+    ->condition('nid', $sandbox['last_nid_processed'], '>')
+    ->orderBy('nid', 'ASC')
+    ->range(0, $limit)
+    ->execute()
+    ->fetchAllAssoc('nid', PDO::FETCH_ASSOC);
+
+  foreach ($webforms as $nid => $webform) {
+    // Update the webform record itself.
+    $original = $webform;
+    $parts = array(
+      'confirmation',
+      'redirect_url',
+    );
+    foreach ($parts as $part) {
+      $webform[$part] = str_replace($patterns, $replacements, $webform[$part]);
+      $webform[$part] = preg_replace($dpatterns, $dreplacements, $webform[$part]);
+    }
+    if ($webform != $original) {
+      drupal_write_record('webform', $webform, array('nid'));
+    }
+
+    // Update tokens in component configurations.
+    $result = db_select('webform_component', 'wc', array('fetch' => PDO::FETCH_ASSOC))
+      ->fields('wc')
+      ->condition('wc.nid', $nid)
+      ->execute();
+    foreach ($result as $component) {
+      $original_extra = $component['extra'];
+      $original_value = $component['value'];
+      $component['extra'] = unserialize($component['extra']);
+      if (isset($component['extra']['description'])) {
+        $description = str_replace($patterns, $replacements, $component['extra']['description']);
+        $description = preg_replace($dpatterns, $dreplacements, $description);
+        $component['extra']['description'] = $description;
+      }
+      $component['extra'] = serialize($component['extra']);
+      $value = str_replace($patterns, $replacements, $component['value']);
+      $value = preg_replace($dpatterns, $dreplacements, $value);
+      $component['value'] = $value;
+      if ($component['extra'] != $original_extra || $component['value'] != $original_value) {
+        drupal_write_record('webform_component', $component, array('nid', 'cid'));
+      }
+    }
+
+    // Update tokens in e-mail configurations.
+    $result = db_select('webform_emails', 'we', array('fetch' => PDO::FETCH_ASSOC))
+      ->fields('we')
+      ->condition('we.nid', $nid)
+      ->execute();
+    foreach ($result as $email) {
+      $original = $email['template'];
+      $template = str_replace($patterns, $replacements, $email['template']);
+      $template = preg_replace($dpatterns, $dreplacements, $template);
+      if ($template != $original) {
+        $email['template'] = $template;
+        drupal_write_record('webform_emails', $email, array('nid', 'eid'));
+      }
+    }
+  }
+
+  // If less than limit was processed, the update process is finished.
+  if (count($webforms) < $limit || $sandbox['progress'] == $sandbox['max']) {
+    $finished = TRUE;
+  }
+
+  // If there's no max value then there's nothing to update and we're finished.
+  if (empty($sandbox['max']) || isset($finished)) {
+    $message = t('Your existing webforms have been upgraded to use the global Drupal 7 token system.');
+    if (!module_exists('token')) {
+      $message .= ' <strong>' . t('Please download and install the <a href="http://drupal.org/project/token" target="_blank">Token module</a>. Otherwise some tokens will not be rendered.') . '</strong>';
+    }
+    return $message;
+  }
+  else {
+    // Indicate our current progress to the batch update system.
+    $sandbox['#finished'] = $sandbox['progress'] / $sandbox['max'];
+  }
+}
diff --git a/webform.module b/webform.module
index 6ff3c85..503f040 100644
--- a/webform.module
+++ b/webform.module
@@ -558,7 +558,7 @@
       'pattern' => 'webform_mail_headers_[0-9]+',
     ),
     'webform_token_help' => array(
-      'variables' => array('groups' => array()),
+      'variables' => array('groups' => array('node')),
     ),
     // webform.admin.inc.
     'webform_admin_settings' => array(
@@ -2516,7 +2516,7 @@
 
   // Clean up the redirect URL and filter it for webform tokens.
   $redirect_url = trim($node->webform['redirect_url']);
-  $redirect_url = _webform_filter_values($redirect_url, $node, $submission, NULL, FALSE, TRUE);
+  $redirect_url = _webform_filter_values($redirect_url, $node, $submission, NULL, FALSE);
 
 
   // Remove the domain name from the redirect.
@@ -2895,176 +2895,24 @@
  *   Boolean value indicating if the results should be run through check_plain.
  *   This is used any time the values will be output as HTML, but not in
  *   default values or e-mails.
- * @param $allow_anonymous
- *   Boolean value indicating if all tokens should be replaced for anonymous
- *   users, even if they contain sensitive user information such as %session or
- *   %ip_address. This is disabled by default to prevent user data from being
- *   preserved in the anonymous page cache and should only be used in
- *   non-cached situations, such as e-mails.
  */
-function _webform_filter_values($string, $node = NULL, $submission = NULL, $email = NULL, $strict = TRUE, $allow_anonymous = FALSE) {
-  global $user;
-  static $replacements;
-
+function _webform_filter_values($string, $node = NULL, $submission = NULL, $email = NULL, $strict = TRUE) {
   // Don't do any filtering if the string is empty.
   if (strlen(trim($string)) == 0) {
     return $string;
   }
 
-  // Setup default token replacements.
-  if (!isset($replacements)) {
-    $replacements['unsafe'] = array();
-    $replacements['safe']['%site'] = variable_get('site_name', 'drupal');
-    $replacements['safe']['%date'] = format_date(REQUEST_TIME, 'large');
+  $token_data = array();
+  if ($node) {
+    $token_data['node'] = $node;
   }
-
-  // Node replacements.
-  if (isset($node) && !array_key_exists('%nid', $replacements['safe'])) {
-    $replacements['safe']['%nid'] = $node->nid;
-    $replacements['safe']['%title'] = $node->title;
+  if ($submission) {
+    $token_data['webform-submission'] = $submission;
   }
-
-  // Determine the display format.
-  $format = isset($email['html']) && $email['html'] ? 'html' : 'text';
-
-  // Submission replacements.
-  if (isset($submission) && !isset($replacements['email'][$format])) {
-    module_load_include('inc', 'webform', 'includes/webform.components');
-
-    // Set the submission ID.
-    $replacements['unsafe']['%sid'] = $submission->sid;
-
-    // E-mails may be sent in two formats, keep tokens separate for each one.
-    $replacements['email'][$format] = array();
-
-    // Populate token values for each component.
-    foreach ($submission->data as $cid => $value) {
-      $component = $node->webform['components'][$cid];
-
-      // Find by form key.
-      $parents = webform_component_parent_keys($node, $component);
-      $form_key = implode('][', $parents);
-      $display_element = webform_component_invoke($component['type'], 'display', $component, $value['value'], $format);
-
-      // Ensure the component is added as a property.
-      $display_element['#webform_component'] = $component;
-
-      if (empty($display_element['#parents'])) {
-        $display_element['#parents'] = array_merge(array('submitted'), $parents);
-      }
-      if (empty($display_element['#id'])) {
-        $display_element['#id'] = drupal_html_id('edit-' . implode('-', $display_element['#parents']));
-      }
-      $replacements['email'][$format]['%email[' . $form_key . ']'] = render($display_element);
-      $display_element['#theme_wrappers'] = array(); // Remove label and wrappers.
-      $replacements['email'][$format]['%value[' . $form_key . ']'] = render($display_element);
-    }
-
-    // Provide blanks for components in the webform but not in the submission.
-    $missing_components = array_diff_key($node->webform['components'], $submission->data);
-    foreach ($missing_components as $component) {
-      $parents = webform_component_parent_keys($node, $component);
-      $form_key = implode('][', $parents);
-      $replacements['email'][$format]['%email[' . $form_key . ']'] = '';
-      $replacements['email'][$format]['%value[' . $form_key . ']'] = '';
-    }
-
-    // Submission edit URL.
-    $replacements['unsafe']['%submission_url'] = url('node/' . $node->nid . '/submission/' . $submission->sid, array('absolute' => TRUE));
+  if ($email) {
+    $token_data['webform-email'] = $email;
   }
-
-  // Token for the entire form tree for e-mails.
-  if (isset($submission) && isset($email)) {
-    $replacements['email'][$format]['%email_values'] = webform_submission_render($node, $submission, $email, $format);
-  }
-
-  // Provide a list of candidates for token replacement.
-  $special_tokens = array(
-    'safe' => array(
-      '%get' => $_GET,
-      '%post' => $_POST,
-    ),
-    'unsafe' => array(
-      '%cookie' => $_COOKIE,
-      '%session' => isset($_SESSION) ? $_SESSION : array(),
-      '%request' => $_REQUEST,
-      '%server' => $_SERVER,
-      '%profile' => (array) $user,
-    ),
-  );
-
-  // Replacements of global variable tokens.
-  if (!isset($replacements['specials_set'])) {
-    $replacements['specials_set'] = TRUE;
-
-    // Load profile information if available.
-    if ($user->uid) {
-      $account = user_load($user->uid);
-      $special_tokens['unsafe']['%profile'] = (array) $account;
-    }
-
-    // User replacements.
-    if (!array_key_exists('%uid', $replacements['unsafe'])) {
-      $replacements['unsafe']['%uid'] = !empty($user->uid) ? $user->uid : '';
-      $replacements['unsafe']['%username'] = isset($user->name) ? $user->name : '';
-      $replacements['unsafe']['%useremail'] = isset($user->mail) ? $user->mail : '';
-      $replacements['unsafe']['%ip_address'] = ip_address();
-    }
-
-    // Populate the replacements array with special variables.
-    foreach ($special_tokens as $safe_state => $tokens) {
-      foreach ($tokens as $token => $variable) {
-        // Safety check in case $_POST or some other global has been removed
-        // by a naughty module, in which case $variable may be NULL.
-        if (!is_array($variable)) {
-          continue;
-        }
-
-        foreach ($variable as $key => $value) {
-          // This special case for profile module dates.
-          if ($token == '%profile' && is_array($value) && isset($value['year'])) {
-            $replacement = webform_strtodate(webform_date_format(), $value['month'] . '/' . $value['day'] . '/' . $value['year'], 'UTC');
-          }
-          else {
-            // Checking for complex types (arrays and objects) fails here with
-            // incomplete objects (see http://php.net/is_object), so we check
-            // for simple types instead.
-            $replacement = (is_string($value) || is_bool($value) || is_numeric($value)) ? $value : '';
-          }
-          $replacements[$safe_state][$token . '[' . $key . ']'] = $replacement;
-        }
-      }
-    }
-  }
-
-  // Make a copy of the replacements so we don't affect the static version.
-  $safe_replacements = $replacements['safe'];
-
-  // Restrict replacements for anonymous users. Not all tokens can be used
-  // because they may expose session or other private data to other users when
-  // anonymous page caching is enabled.
-  if ($user->uid || $allow_anonymous) {
-    $safe_replacements += $replacements['unsafe'];
-    if (isset($replacements['email'][$format])) {
-      $safe_replacements += $replacements['email'][$format];
-    }
-  }
-  else {
-    foreach ($replacements['unsafe'] as $key => $value) {
-      $safe_replacements[$key] = '';
-    }
-  }
-
-  $find = array_keys($safe_replacements);
-  $replace = array_values($safe_replacements);
-  $string = str_replace($find, $replace, $string);
-
-  // Clean up any unused tokens.
-  foreach ($special_tokens as $safe_state => $tokens) {
-    foreach (array_keys($tokens) as $token) {
-      $string = preg_replace('/\\' . $token . '\[\w+\]/', '', $string);
-    }
-  }
+  $string = token_replace($string, $token_data, array('clear' => true));
 
   return $strict ? _webform_filter_xss($string) : $string;
 }
@@ -3170,7 +3018,7 @@
       $result = variable_get('webform_default_from_address', variable_get('site_mail', ini_get('sendmail_from')));
       break;
     case 'webform_default_subject':
-      $result = variable_get('webform_default_subject', t('Form submission from: %title'));
+      $result = variable_get('webform_default_subject', t('Form submission from: [node:title]'));
       break;
     case 'webform_node_types':
       $result = variable_get('webform_node_types', array('webform'));
@@ -3184,81 +3032,29 @@
 
 function theme_webform_token_help($variables) {
   $groups = $variables['groups'];
-  $groups = empty($groups) ? array('basic', 'node', 'special') : $groups;
-
-  static $tokens = array();
-
-  if (empty($tokens)) {
-    $tokens['basic'] = array(
-      'title' => t('Basic tokens'),
-      'tokens' => array(
-        '%username' => t('The name of the user if logged in. Blank for anonymous users.'),
-        '%useremail' => t('The e-mail address of the user if logged in. Blank for anonymous users.'),
-        '%ip_address' => t('The IP address of the user.'),
-        '%site' => t('The name of the site (i.e. %site_name)', array('%site_name' => variable_get('site_name', ''))),
-        '%date' => t('The current date, formatted according to the site settings.'),
-      ),
-    );
-
-    $tokens['node'] = array(
-      'title' => t('Node tokens'),
-      'tokens' => array(
-        '%nid' => t('The node ID.'),
-        '%title' => t('The node title.'),
-      ),
-    );
-
-    $tokens['special'] = array(
-      'title' => t('Special tokens'),
-      'tokens' => array(
-        '%profile[' . t('key') . ']' => t('Any user profile field or value, such as %profile[name] or %profile[profile_first_name]'),
-        '%get[' . t('key') . ']' => t('Tokens may be populated from the URL by creating URLs of the form http://example.com/my-form?foo=bar. Using the token %get[foo] would print "bar".'),
-        '%post[' . t('key') . ']' => t('Tokens may also be populated from POST values that are submitted by forms.'),
-      ),
-      'description' => t('In addition to %get and %post, the following super tokens may be used, though only with logged-in users: %server, %cookie, and %request. For example %server[HTTP_USER_AGENT] or %session[id].'),
-    );
-
-    $tokens['email'] = array(
-      'title' => t('E-mail tokens'),
-      'tokens' => array(
-        '%email_values' => t('All included components in a hierarchical structure.'),
-        '%email[' . t('key') . '] ' => t('A formatted value and field label. Elements may be accessed such as <em>%email[fieldset_a][key_b]</em>. Do not include quotes.'),
-        '%submission_url' => t('The URL for viewing the completed submission.'),
-      ),
-    );
-
-    $tokens['submission'] = array(
-      'title' => t('Submission tokens'),
-      'tokens' => array(
-        '%sid' => t('The unique submission ID.'),
-        '%value[key]' => t('A value without additional formatting. Elements may be accessed such as <em>%value[fieldset_a][key_b]</em>. Do not include quotes.'),
-      ),
-    );
-  }
-
-  $output = '';
-  $output .= '<p>' . t('You may use special tokens in this field that will be replaced with dynamic values.') . '</p>';
-
-  foreach ($tokens as $group_name => $group) {
-    if (!is_array($groups) || in_array($group_name, $groups)) {
-      $items = array();
-      foreach ($group['tokens'] as $token => $token_description) {
-        $items[] = $token . ' - ' . $token_description;
-      }
-      $output .= theme('item_list', array('items' => $items, 'title' => $group['title']));
-      $output .= isset($group['description']) ? '<p>' . $group['description']  . '</p>' : '';
-    }
-  }
 
   $fieldset = array(
     '#title' => t('Token values'),
     '#type' => 'fieldset',
     '#collapsible' => TRUE,
     '#collapsed' => TRUE,
-    '#children' => '<div>' . $output . '</div>',
     '#attributes' => array('class' => array('collapsible', 'collapsed')),
   );
-  return theme('fieldset', array('element' => $fieldset));
+
+  $help = '<p>' . t('This field supports dynamic token values. Common values might be [current-user:mail] or [node:title].') . '</p>';
+  if (!module_exists('token')) {
+    $help .= '<p>' . t('A full listing of tokens may be listed here by installing the <a href="http://drupal.org/project/token">Token module</a>.') . '</p>';
+  }
+  $fieldset['help'] = array(
+    '#markup' => $help,
+  );
+
+  $fieldset['token_tree'] = array(
+    '#theme' => 'token_tree',
+    '#token_types' => $groups,
+  );
+
+  return render($fieldset);
 }
 
 function _webform_safe_name($name) {
@@ -3344,15 +3140,15 @@
   // Address may be an array if a component value was used on checkboxes.
   if (is_array($address)) {
     foreach ($address as $key => $individual_address) {
-      $address[$key] = _webform_filter_values($individual_address, $node, $submission, NULL, FALSE, TRUE);
+      $address[$key] = _webform_filter_values($individual_address, $node, $submission, NULL, FALSE);
     }
   }
   else {
-    $address = _webform_filter_values($address, $node, $submission, NULL, FALSE, TRUE);
+    $address = _webform_filter_values($address, $node, $submission, NULL, FALSE);
   }
 
   if ($format == 'long' && !empty($name)) {
-    $name = _webform_filter_values($name, $node, $submission, NULL, FALSE, TRUE);
+    $name = _webform_filter_values($name, $node, $submission, NULL, FALSE);
     if ($encode) {
       $name = mime_header_encode($name);
     }
diff --git a/webform.tokens.inc b/webform.tokens.inc
new file mode 100644
index 0000000..e3b9409
--- /dev/null
+++ b/webform.tokens.inc
@@ -0,0 +1,148 @@
+<?php
+
+/**
+ * @file
+ * Builds placeholder replacement tokens for webform-related data.
+ */
+
+/**
+ * Implements hook_token_info().
+ */
+function webform_token_info() {
+  // Webform submission tokens.
+  $info['types']['webform-submission'] = array(
+    'name' => t('Submission'),
+    'description' => t('Tokens related to webform submissions.'),
+    'needs-data' => 'webform-submission',
+  );
+  $info['tokens']['webform-submission']['sid'] = array(
+    'name' => t('Submission ID'),
+    'description' => t('The unique indentifier for the webform submission.'),
+  );
+  $info['tokens']['webform-submission']['date'] = array(
+    'name' => t('Date submitted'),
+    'description' => t('The date the webform was submitted.'),
+    'type' => 'date',
+  );
+  $info['tokens']['webform-submission']['ip-address'] = array(
+    'name' => t('IP address'),
+    'description' => t('The IP address that was used when submitting the webform.'),
+  );
+  $info['tokens']['webform-submission']['user'] = array(
+    'name' => t('Submitter'),
+    'description' => t('The user that submitted the webform result.'),
+    'type' => 'user',
+  );
+  $info['tokens']['webform-submission']['url'] = array(
+    'name' => t('URL'),
+    'description' => t('Webform tokens related to URLs.'),
+    'type' => 'url',
+  );
+  $info['tokens']['webform-submission']['email-values'] = array(
+    'name' => t('Webform submission values'),
+    'description' => t('All included components in a hierarchical structure.'),
+  );
+  $info['tokens']['webform-submission']['email'] = array(
+    'name' => t('Webform label and value'),
+    'description' => t('A formatted value and field label.'),
+    'dynamic' => TRUE,
+  );
+  $info['tokens']['webform-submission']['submission'] = array(
+    'name' => t('Webform submission component value'),
+    'description' => t('Webform tokens from submitted data. Replace the "?" with the "Field Key" of the component that contains the appropriate values.'),
+    'dynamic' => TRUE,
+  );
+
+  return $info;
+}
+
+/**
+ * Implements hook_tokens().
+ */
+function webform_tokens($type, $tokens, array $data = array(), array $options = array()) {
+  $replacements = array();
+
+  $url_options = array('absolute' => TRUE);
+  if (isset($options['language'])) {
+    $url_options['language'] = $options['language'];
+    $language_code = $options['language']->language;
+  }
+  else {
+    $language_code = NULL;
+  }
+
+  $sanitize = !empty($options['sanitize']);
+
+  // Webform tokens  (caching globally)
+  if ($type == 'webform-submission' && !empty($data['webform-submission'])) {
+    $submission = $data['webform-submission'];
+
+    foreach ($tokens as $name => $original) {
+      switch ($name) {
+        case 'sid':
+          $replacements[$original] = $submission->sid;
+          break;
+        case 'date':
+          $replacements[$original] = format_date($submission->submitted, 'medium', '', NULL, $language_code);
+          break;
+        case 'ip-address':
+          $replacements[$original] = $sanitize ? check_plain($submission->remote_addr) : $submission->remote_addr;
+          break;
+        case 'user':
+          $account = user_load($submission->uid);
+          $name = format_username($account);
+          $replacements[$original] = $sanitize ? check_plain($name) : $name;
+          break;
+        case 'url':
+          $replacements[$original] = url("node/{$submission->nid}/submission/{$submission->sid}", $url_options);
+          break;
+        case 'edit-url':
+          $replacements[$original] = url("node/{$submission->nid}/submission/{$submission->sid}/edit", $url_options);
+          break;
+        case 'email-values':
+          $email = $data['webform-email'];
+          // Token for the entire form tree for e-mails.
+          if (!empty($email)) {
+            $node = node_load($submission->nid);
+            $format = isset($email['html']) && $email['html'] ? 'html' : 'text';
+            $replacements[$original] = webform_submission_render($node, $submission, $email, $format);
+          }
+          break;
+        default: // Webform submission tokens (caching per webform node)
+          $split = explode(':', $name);
+          $skey = $split[1];
+          $node = node_load($submission->nid);
+          foreach ($node->webform['components'] as $ckey => $cvalue) {
+            if ($cvalue['form_key'] == $skey) {
+              if ($split[0] == 'submission') {
+                $replacements[$original] = $submission->data[$ckey]['value'][0];
+              }
+              elseif ($split[0] == 'email') {
+                $replacements[$original] = $cvalue['name'] . ': ' . $submission->data[$ckey]['value'][0];
+              }
+            }
+            else {
+              continue;
+            }
+          }
+          break;
+      }
+    }
+
+    // Chained token relationships.
+    if (($node_tokens = token_find_with_prefix($tokens, 'node')) && $node = node_load($submission->nid)) {
+      $replacements = token_generate('node', $node_tokens, array('node' => $node), $options);
+    }
+    if ($date_tokens = token_find_with_prefix($tokens, 'date')) {
+      $replacements = token_generate('date', $date_tokens, array('date' => $submission->submitted), $options);
+    }
+    if (($user_tokens = token_find_with_prefix($tokens, 'user')) && $account = user_load($submission->uid)) {
+      $replacements = token_generate('user', $user_tokens, array('user' => $account), $options);
+    }
+    if ($url_tokens = token_find_with_prefix($tokens, 'url')) {
+      $replacements = token_generate('url', $url_tokens, array('path' => url("node/{$submission->nid}/submission/{$submission->sid}", $url_options)), $options);
+    }
+  }
+
+  return $replacements;
+}
\ No newline at end of file