Index: token_example/token_example.info
===================================================================
RCS file: token_example/token_example.info
diff -N token_example/token_example.info
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ token_example/token_example.info	10 Mar 2010 14:55:34 -0000
@@ -0,0 +1,9 @@
+; $Id:$
+name = Token example
+description = An example module showing how to define and use tokens.
+package = Example modules
+version = VERSION
+core = 7.x
+files[] = token_example.module
+files[] = token_example.tokens.inc
+files[] = token_example.test
Index: token_example/token_example.module
===================================================================
RCS file: token_example/token_example.module
diff -N token_example/token_example.module
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ token_example/token_example.module	10 Mar 2010 14:55:34 -0000
@@ -0,0 +1,184 @@
+<?php
+// $Id$
+
+/**
+ * Implements hook_menu().
+ */
+function token_example_menu() {
+  $items['examples/token'] = array(
+    'title' => 'Token example',
+    'description' => 'Test replacement tokens in real time.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('token_example_example_form'),
+    'access callback' => TRUE,
+  );
+  return $items;
+}
+
+/**
+ * Implements hook_entity_info_alter().
+ */
+function token_entity_info_alter(&$info) {
+  // Since some token types to do not match their entity type names, we have t
+  // map them to the proper type.
+  $info['taxonomy_term']['token type'] = 'term';
+  $info['taxonomy_vocabulary']['token type'] = 'vocabulary';
+}
+
+/**
+ * Form builder; display lists of supported token entities and text to tokenize.
+ */
+function token_example_example_form(&$form, $form_state) {
+  $entities = entity_get_info();
+  $token_types = array();
+
+  // Scan through the list of entities for supported token entities.
+  foreach ($entities as $entity => $info) {
+    $object_callback = "_token_example_get_{$entity}";
+    if (function_exists($object_callback) && $objects = $object_callback()) {
+      $form[$entity] = array(
+        '#type' => 'select',
+        '#title' => $info['label'],
+        '#options' => array(0 => t('Not selected')) + $objects,
+        '#default_value' => isset($form_state['storage'][$entity]) ? $form_state['storage'][$entity] : 0,
+        '#access' => !empty($objects),
+      );
+
+      // Build a list of supported token types based on the available entites.
+      if ($form[$entity]['#access']) {
+        $token_types[$entity] = !empty($info['token type']) ? $info['token type'] : $entity;
+      }
+    }
+  }
+
+  $form['text'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Enter your text here'),
+    '#default_value' => 'Hello [current-user:name]!'
+  );
+
+  // Display the results of tokenized text.
+  if (!empty($form_state['storage']['text'])) {
+    $form['text']['#default_value'] = $form_state['storage']['text'];
+
+    $data = array();
+    foreach ($entities as $entity => $info) {
+      if (!empty($form_state['storage'][$entity])) {
+        $objects = entity_load($entity, array($form_state['storage'][$entity]));
+        if ($objects) {
+          $data[$token_types[$entity]] = reset($objects);
+        }
+      }
+    }
+
+    // Display the tokenized text.
+    $form['text_tokenized'] = array(
+      '#type' => 'item',
+      '#title' => t('Result'),
+      '#markup' => token_replace($form_state['storage']['text'], $data),
+    );
+  }
+
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Submit'),
+  );
+
+  $form['token_tree'] = array(
+    '#theme' => 'token_tree',
+    '#token_types' => $token_types,
+  );
+
+  return $form;
+}
+
+/**
+ * Submit callback; store the submitted values into storage.
+ */
+function token_example_example_form_submit($form, &$form_state) {
+  $form_state['storage'] = $form_state['values'];
+  $form_state['rebuild'] = TRUE;
+}
+
+/**
+ * Build a list of available content.
+ */
+function _token_example_get_node() {
+  if (!user_access('access content') && !user_access('bypass node access')) {
+    return array();
+  }
+
+  $node_query = db_select('node', 'n');
+  $node_query->fields('n', array('nid', 'title'));
+  $node_query->condition('n.status', NODE_PUBLISHED);
+  $node_query->orderBy('n.created', 'DESC');
+  $node_query->range(0, 10);
+  $node_query->addTag('node_access');
+  $nodes = $node_query->execute()->fetchAllKeyed();
+  $nodes = array_map('check_plain', $nodes);
+  return $nodes;
+}
+
+/**
+ * Build a list of available comments.
+ */
+function _token_example_get_comment() {
+  if (!module_exists('comment') || (!user_access('access comments') && !user_access('administer comments'))) {
+    return array();
+  }
+
+  $comment_query = db_select('comment', 'c');
+  $comment_query->innerJoin('node', 'n', 'n.nid = c.nid');
+  $comment_query->fields('c', array('cid', 'subject'));
+  $comment_query->condition('n.status', NODE_PUBLISHED);
+  $comment_query->condition('c.status', COMMENT_PUBLISHED);
+  $comment_query->orderBy('c.created', 'DESC');
+  $comment_query->range(0, 10);
+  $comment_query->addTag('node_access');
+  $comments = $comment_query->execute()->fetchAllKeyed();
+  $comments = array_map('check_plain', $comments);
+  return $comments;
+}
+
+/**
+ * Build a list of availalbe user accounts.
+ */
+function _token_example_get_user() {
+  if (!user_access('access user profiles') && !user_access('administer users')) {
+    return array();
+  }
+
+  $account_query = db_select('users', 'u');
+  $account_query->fields('u', array('uid', 'name'));
+  $account_query->condition('u.uid', 0, '>');
+  $account_query->condition('u.status', 1);
+  $account_query->range(0, 10);
+  $accounts = $account_query->execute()->fetchAllKeyed();
+  $accounts = array_map('check_plain', $accounts);
+  return $accounts;
+}
+
+/**
+ * Build a list of availalbe taxonomy terms.
+ */
+function _token_example_get_taxonomy_term() {
+  $term_query = db_select('taxonomy_term_data', 'ttd');
+  $term_query->fields('ttd', array('tid', 'name'));
+  $term_query->range(0, 10);
+  $term_query->addTag('term_access');
+  $terms = $term_query->execute()->fetchAllKeyed();
+  $terms = array_map('check_plain', $terms);
+  return $terms;
+}
+
+/**
+ * Build a list of availalbe files.
+ */
+function _token_example_get_file() {
+  $file_query = db_select('file', 'f');
+  $file_query->fields('f', array('fid', 'filename'));
+  $file_query->range(0, 10);
+  $files = $file_query->execute()->fetchAllKeyed();
+  $files = array_map('check_plain', $files);
+  return $files;
+}
Index: token_example/token_example.test
===================================================================
RCS file: token_example/token_example.test
diff -N token_example/token_example.test
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ token_example/token_example.test	10 Mar 2010 14:55:34 -0000
@@ -0,0 +1,63 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Test cases for Testing the token example module.
+ */
+
+class TokenExampleTestCase extends DrupalWebTestCase {
+  protected $web_user;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Token example functionality',
+      'description' => 'Verify the token example interface.',
+      'group' => 'Examples',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('token_example');
+    $this->web_user = $this->drupalCreateUser();
+    $this->drupalLogin($this->web_user);
+  }
+
+  function testInterface() {
+    $filtered_id = db_query("SELECT format FROM {filter_format} WHERE name = 'Filtered HTML'")->fetchField();
+    $plain_id = db_query("SELECT format FROM {filter_format} WHERE name = 'Plain text'")->fetchField();
+
+    $this->drupalGet('examples/token');
+    $this->assertNoFieldByName('node');
+    $this->assertNoFieldByName('user');
+
+    $edit = array(
+      'text' => 'User [current-user:name] is trying the token example!',
+    );
+    $this->drupalPost(NULL, $edit, t('Submit'));
+    $this->assertText('User ' . $this->web_user->name . ' is trying the token example!');
+
+    // Create a node and then make the 'Plain text' text format the default.
+    $node = $this->drupalCreateNode(array('title' => 'Example node', 'status' => NODE_PUBLISHED));
+    db_update('filter_format')
+      ->fields(array('weight' => -10))
+      ->condition('name', 'Plain text')
+      ->execute();
+
+    $this->drupalGet('examples/token');
+    $this->assertFieldByName('node');
+
+    $edit = array(
+      'text' => 'Would you like to view the [node:type-name] [node:title] with text format [node:body-format] (ID [node:body-format:id])?',
+      'node' => $node->nid,
+    );
+    $this->drupalPost(NULL, $edit, t('Submit'));
+    $this->assertText('Would you like to view the Basic page Example node with text format Filtered HTML (ID ' . $filtered_id . ')?');
+
+    $edit = array(
+      'text' => 'Your default text format is [default-format:name] (ID [default-format:id]).',
+    );
+    $this->drupalPost(NULL, $edit, t('Submit'));
+    $this->assertText('Your default text format is Plain text (ID ' . $plain_id . ')');
+  }
+}
Index: token_example/token_example.tokens.inc
===================================================================
RCS file: token_example/token_example.tokens.inc
diff -N token_example/token_example.tokens.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ token_example/token_example.tokens.inc	10 Mar 2010 14:55:35 -0000
@@ -0,0 +1,130 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Token callbacks for the token_example module.
+ */
+
+/**
+ * Implements hook_token_info().
+ */
+function token_example_token_info() {
+  // Add two different token types. The first is the generic text format. The
+  // second is the user's default text format, which is itself a 'format' token
+  // type so it can be used directly.
+  $info['types']['format'] = array(
+    'name' => t('Text formats'),
+    'description' => t('Tokens related to text formats.'),
+    'needs-data' => 'format',
+  );
+  $info['types']['default-format'] = array(
+    'name' => t('Default text format'),
+    'description' => t("Tokens related to the currently logged in user's default text format."),
+    'type' => 'format',
+  );
+
+  // Tokens for the text format token type.
+  $info['tokens']['format']['id'] = array(
+    'name' => t('ID'),
+    'description' => t("The unique ID of the text format."),
+  );
+  $info['tokens']['format']['name'] = array(
+    'name' => t('Name'),
+    'description' => t("The name of the text format."),
+  );
+
+  // Node tokens.
+  $info['tokens']['node']['body-format'] = array(
+    'name' => t('Body text format'),
+    'description' => t("The name of the text format used on the node's body field."),
+    'type' => 'format',
+  );
+
+  // Comment tokens.
+  if (module_exists('comment')) {
+    $info['tokens']['comment']['body-format'] = array(
+      'name' => t('Body text format'),
+      'description' => t("The name of the text format used on the comment's body field."),
+      'type' => 'format',
+    );
+  }
+
+  return $info;
+}
+
+/**
+ * Implements hook_tokens().
+ */
+function token_example_tokens($type, $tokens, array $data = array(), array $options = array()) {
+  $replacements = array();
+  $sanitize = !empty($options['sanitize']);
+
+  // Text format tokens.
+  if ($type == 'format' && !empty($data['format'])) {
+    $format = $data['format'];
+
+    foreach ($tokens as $name => $original) {
+      switch ($name) {
+        case 'id':
+          // Since {filter_format}.format is an integer and not user-entered
+          // text, it does not need to ever be sanitized.
+          $replacements[$original] = $format->format;
+          break;
+        case 'name':
+          // Since the format name is user-entered text, santize when requested.
+          $replacements[$original] = $sanitize ? filter_xss($format->name) : $format->name;
+          break;
+      }
+    }
+  }
+
+  // Default format tokens.
+  if ($type == 'default-format') {
+    $default_id = filter_default_format();
+    $default_format = filter_format_load($default_id);
+    $replacements += token_generate('format', $tokens, array('format' => $default_format), $options);
+  }
+
+  // Node tokens.
+  if ($type == 'node' && !empty($data['node'])) {
+    $node = $data['node'];
+
+    foreach ($tokens as $name => $original) {
+      switch ($name) {
+        case 'body-format':
+          $format = filter_format_load($node->body[LANGUAGE_NONE][0]['format']);
+          $replacements[$original] = $sanitize ? filter_xss($format->name) : $format->name;
+          break;
+      }
+    }
+
+    // Chained token relationships.
+    if ($format_tokens = token_find_with_prefix($tokens, 'body-format')) {
+      $body_format = filter_format_load($node->body[LANGUAGE_NONE][0]['format']);
+      $replacements += token_generate('format', $format_tokens, array('format' => $body_format), $options);
+    }
+  }
+
+  // Comment tokens.
+  if ($type == 'comment' && !empty($data['comment'])) {
+    $comment = $data['comment'];
+
+    foreach ($tokens as $name => $original) {
+      switch ($name) {
+        case 'body-format':
+          $format = filter_format_load($comment->comment_body[LANGUAGE_NONE][0]['format']);
+          $replacements[$original] = $sanitize ? filter_xss($format->name) : $format->name;
+          break;
+      }
+    }
+
+    // Chained token relationships.
+    if ($format_tokens = token_find_with_prefix($tokens, 'body-format')) {
+      $body_format = filter_format_load($comment->comment_body[LANGUAGE_NONE][0]['format']);
+      $replacements += token_generate('format', $format_tokens, array('format' => $body_format), $options);
+    }
+  }
+
+  return $replacements;
+}
