You are here

token.module in Token 5

Same filename and directory in other branches
  1. 8 token.module
  2. 6 token.module
  3. 7 token.module

The Token API module.

The Token module provides an API for providing tokens to other modules. Tokens are small bits of text that can be placed into larger documents via simple placeholders, like %site-name or [user].

File

token.module
View source
<?php

/**
 * @file
 * The Token API module.
 *
 * The Token module provides an API for providing tokens to other modules.
 * Tokens are small bits of text that can be placed into larger documents
 * via simple placeholders, like %site-name or [user].
 *
 * @ingroup token
 */

/**
 * The default token prefix string.
 */
define('TOKEN_PREFIX', '[');

/**
 * The default token suffix string.
 */
define('TOKEN_SUFFIX', ']');

/**
 * Implements hook_help().
 */
function token_help($section) {
  if ($section == 'admin/help#token') {
    $output = '<dl>';
    $output .= '<dt>' . t('List of the currently available tokens on this site') . '</dt>';
    $output .= '<dd>' . theme('token_help', 'all') . '</dd>';
    $output .= '</dl>';
    return $output;
  }
}

/**
 * For a given context, builds a formatted list of tokens and descriptions
 * of their replacement values.
 *
 * @param type
 *    The token types to display documentation for. Defaults to 'all'.
 * @param prefix
 *    The prefix your module will use when parsing tokens. Defaults to '['
 * @param suffix
 *    The suffix your module will use when parsing tokens. Defaults to ']'
 * @return An HTML table containing the formatting docs.
 */
function theme_token_help($type = 'all', $prefix = '[', $suffix = ']') {
  token_include();
  $full_list = token_get_list($type);
  $headers = array(
    t('Token'),
    t('Replacement value'),
  );
  $rows = array();
  foreach ($full_list as $key => $category) {
    $rows[] = array(
      array(
        'data' => drupal_ucfirst($key) . ' ' . t('tokens'),
        'class' => 'region',
        'colspan' => 2,
      ),
    );
    foreach ($category as $token => $description) {
      $row = array();
      $row[] = $prefix . $token . $suffix;
      $row[] = $description;
      $rows[] = $row;
    }
  }
  $output = theme('table', $headers, $rows, array(
    'class' => 'description',
  ));
  return $output;
}

/**
 * Sample implementation of hook_token_values().
 *
 * @param type
 *   A flag indicating the class of substitution tokens to use. If an
 *   object is passed in the second param, 'type' should contain the
 *   object's type. For example, 'node', 'comment', or 'user'. If your
 *   implementation of the hook inserts globally applicable tokens that
 *   do not depend on a particular object, it should only return values
 *   when $type is 'global'.
 * @param object
 *   Optionally, the object to use for building substitution values.
 *   A node, comment, user, etc.
 * @return
 *   A keyed array containing the substitution tokens and the substitution
 *   values for the passed-in type and object.
 */
function token_token_values($type, $object = NULL) {
  global $user;
  $values = array();
  switch ($type) {
    case 'global':

      // Current user tokens.
      $values['user-name'] = $user->uid ? $user->name : variable_get('anonymous', t('Anonymous'));
      $values['user-id'] = $user->uid ? $user->uid : 0;
      $values['user-mail'] = $user->uid ? $user->mail : '';

      // Site information tokens.
      $values['site-url'] = url('<front>', NULL, NULL, TRUE);
      $values['site-name'] = check_plain(variable_get('site_name', t('Drupal')));
      $values['site-slogan'] = check_plain(variable_get('site_slogan', ''));
      $values['site-mission'] = filter_xss_admin(variable_get('site_mission', ''));
      $values['site-mail'] = variable_get('site_mail', '');
      $values += token_get_date_token_values(NULL, 'site-date-');
      $values['site-date'] = $values['site-date-small'];

      // Current page tokens.
      $values['current-page-title'] = drupal_get_title();
      $alias = drupal_get_path_alias($_GET['q']);
      $values['current-page-path'] = $alias;
      $values['current-page-path-raw'] = check_plain($alias);
      $values['current-page-url'] = url($_GET['q'], NULL, NULL, TRUE);
      $page = isset($_GET['page']) ? $_GET['page'] : '';
      $pager_page_array = explode(',', $page);
      $page = $pager_page_array[0];
      $values['current-page-number'] = (int) $page + 1;
      $values['page-number'] = $values['current-page-number'];
      break;
  }
  return $values;
}

/**
 * Sample implementation of hook_token_list(). Documents the individual
 * tokens handled by your module.
 *
 * @param type
 *   A flag indicating the class of substitution tokens to return
 *   information on. If this is set to 'all', a complete list is being
 *   built and your module should return its full list, regardless of
 *   type. Global tokens should always be returned, regardless of the
 *   $type passed in.
 * @return
 *   A keyed array listing the substitution tokens. Elements should be
 *   in the form of: $list[$type][$token] = $description
 */
function token_token_list($type = 'all') {
  $tokens = array();
  if ($type == 'global' || $type == 'all') {

    // Current user tokens.
    $tokens['global']['user-name'] = t('The name of the currently logged in user.');
    $tokens['global']['user-id'] = t('The user ID of the currently logged in user.');
    $tokens['global']['user-mail'] = t('The email address of the currently logged in user.');

    // Site information tokens.
    $tokens['global']['site-url'] = t("The URL of the site's front page.");
    $tokens['global']['site-name'] = t('The name of the site.');
    $tokens['global']['site-slogan'] = t('The slogan of the site.');
    $tokens['global']['site-mission'] = t("The optional 'mission' of the site.");
    $tokens['global']['site-mail'] = t('The administrative email address for the site.');
    $tokens['global'] += token_get_date_token_info(t('The current'), 'site-date-');

    // Current page tokens.
    $tokens['global']['current-page-title'] = t('The title of the current page.');
    $tokens['global']['current-page-path'] = t('The URL alias of the current page.');
    $tokens['global']['current-page-path-raw'] = t('The URL alias of the current page.');
    $tokens['global']['current-page-url'] = t('The URL of the current page.');
    $tokens['global']['current-page-number'] = t('The page number of the current page when viewing paged lists.');
  }
  return $tokens;
}

/**
 * General function to include the files that token relies on for the real work.
 */
function token_include() {
  static $run = FALSE;
  if (!$run) {
    $run = TRUE;
    $path = drupal_get_path('module', 'token');
    $modules_enabled = array_keys(module_list());
    $modules = array(
      'node',
      'user',
      'taxonomy',
      'comment',
      'content',
    );
    $modules = array_intersect($modules, $modules_enabled);
    foreach ($modules as $module) {
      if ($module == 'content') {
        $module = 'cck';
      }
      require_once "{$path}/token_{$module}.inc";
    }
  }
}

/**
 * Return the value of $original, with all instances of placeholder
 * tokens replaced by their proper values. To replace multiple types
 * at once see token_replace_multiple().
 *
 * @param original
 *  A string, or an array of strings, to perform token substitutions
 *  on.
 * @param type
 *   A flag indicating the class of substitution tokens to use. If an
 *   object is passed in the second param, 'type' should contain the
 *   object's type. For example, 'node', 'comment', or 'user'. If no
 *   type is specified, only 'global' site-wide substitution tokens are
 *   built.
 * @param object
 *   Optionally, the object to use for building substitution values.
 *   A node, comment, user, etc.
 * @param leading
 *    Character(s) to prepend to the token key before searching for
 *    matches. Defaults to an open-bracket.
 * @param trailing
 *    Character(s) to append to the token key before searching for
 *    matches. Defaults to a close-bracket.
 * @param flush
 *    A flag indicating whether or not to flush the token cache.
 *    Useful for processes that need to slog through huge numbers
 *    of tokens in a single execution cycle. Flushing it will
 *    keep them from burning through memory.
 *    The default is FALSE.
 * @return The modified version of $original, with all substitutions
 *   made.
 */
function token_replace($original, $type = 'global', $object = NULL, $leading = TOKEN_PREFIX, $trailing = TOKEN_SUFFIX, $options = array(), $flush = FALSE) {
  $full = token_get_values($type, $object, $flush, $options);
  return _token_replace_tokens($original, $full->tokens, $full->values, $leading, $trailing);
}

/**
 * Return the value of $original, with all instances of placeholder
 * tokens replaced by their proper values. Contrary to token_replace(),
 * this function supports replacing multiple types.
 *
 * @param original
 *  A string, or an array of strings, to perform token substitutions
 *  on.
 * @param types
 *   An array of substitution classes and optional objects. The key is
 *   a flag indicating the class of substitution tokens to use.
 *   If an object is passed as value, the key should contain the
 *   object's type. For example, 'node', 'comment', or 'user'. The
 *   object will be used for building substitution values. If no type
 *   is specified, only 'global' site-wide substitution tokens are built.
 * @param leading
 *    Character(s) to prepend to the token key before searching for
 *    matches. Defaults to an open-bracket.
 * @param trailing
 *    Character(s) to append to the token key before searching for
 *    matches. Defaults to a close-bracket.
 * @param flush
 *    A flag indicating whether or not to flush the token cache.
 *    Useful for processes that need to slog through huge numbers
 *    of tokens in a single execution cycle. Flushing it will
 *    keep them from burning through memory.
 *    The default is FALSE.
 * @return The modified version of $original, with all substitutions
 *   made.
 */
function token_replace_multiple($original, $types = array(
  'global' => NULL,
), $leading = TOKEN_PREFIX, $trailing = TOKEN_SUFFIX, $options = array(), $flush = FALSE) {
  $full = new stdClass();
  $full->tokens = $full->values = array();
  foreach ($types as $type => $object) {
    $temp = token_get_values($type, $object, $flush, $options);
    $full->tokens = array_merge($full->tokens, $temp->tokens);
    $full->values = array_merge($full->values, $temp->values);
  }
  return _token_replace_tokens($original, $full->tokens, $full->values, $leading, $trailing);
}

// Internally used function to replace tokens.
function _token_replace_tokens($original, $tokens, $values, $leading, $trailing) {
  $tokens = token_prepare_tokens($tokens, $leading, $trailing);
  return str_replace($tokens, $values, $original);
}

/**
 * Return a list of valid substitution tokens and their values for
 * the specified type.
 *
 * @param type
 *   A flag indicating the class of substitution tokens to use. If an
 *   object is passed in the second param, 'type' should contain the
 *   object's type. For example, 'node', 'comment', or 'user'. If no
 *   type is specified, only 'global' site-wide substitution tokens are
 *   built.
 * @param object
 *   Optionally, the object to use for building substitution values.
 *   A node, comment, user, etc.
 * @param flush
 *   A flag indicating whether or not to flush the token cache.
 *   Useful for processes that need to slog through huge numbers
 *   of tokens in a single execution cycle. Flushing it will
 *   keep them from burning through memory.
 *   The default is FALSE.
 * @return
 *   A keyed array containing the substitution tokens and the substitution
 *   values for the passed-in type and object.
 */
function token_get_values($type = 'global', $object = NULL, $flush = FALSE, $options = array()) {
  static $tokens;
  static $running;

  // Flush the static token cache. Useful for processes that need to slog through
  // huge numbers of tokens in a single execution cycle. Flushing it will keep
  // them from burning through memory.
  if ($flush || !isset($tokens)) {
    $tokens = array();
  }

  // Since objects in PHP5 are always passed by reference, we ensure we're
  // working on a copy of the object.
  if (is_object($object)) {
    $object = drupal_clone($object);
  }

  // Simple recursion check. This is to avoid content_view()'s potential
  // for endless looping when a filter uses tokens, which load the content
  // view, which calls the filter, which uses tokens, which...
  if ($running) {

    // We'll allow things to get two levels deep, but bail out after that
    // without performing any substitutions.
    $result = new stdClass();
    $result->tokens = array();
    $result->values = array();
    return $result;
  }
  $running = TRUE;
  token_include();
  $id = _token_get_id($type, $object);
  if ($id && isset($tokens[$type][$id])) {
    $tmp_tokens = $tokens[$type][$id];
  }
  else {
    $tmp_tokens = module_invoke_all('token_values', $type, $object, $options);
    $tokens[$type][$id] = $tmp_tokens;
  }

  // Special-case global tokens, as we always want to be able to process
  // those substitutions.
  if (!isset($tokens['global']['default'])) {
    $tokens['global']['default'] = module_invoke_all('token_values', 'global');
  }
  $all = array_merge($tokens['global']['default'], $tokens[$type][$id]);
  $result = new stdClass();
  $result->tokens = array_keys($all);
  $result->values = array_values($all);
  $running = FALSE;
  return $result;
}

/**
 * A helper function that retrieves all currently exposed tokens,
 * and merges them recursively. This is only necessary when building
 * the token listing -- during actual value replacement, only tokens
 * in a particular domain are requested and a normal array_marge() is
 * sufficient.
 *
 * @param types
 *   A flag indicating the class of substitution tokens to use. If an
 *   object is passed in the second param, 'types' should contain the
 *   object's type. For example, 'node', 'comment', or 'user'. 'types'
 *   may also be an array of types of the form array('node','user'). If no
 *   type is specified, only 'global' site-wide substitution tokens are
 *   built.
 * @return
 *   The array of usable tokens and their descriptions, organized by
 *   token type.
 */
function token_get_list($types = 'all') {
  token_include();
  $return = array();
  settype($types, 'array');
  foreach (module_implements('token_list') as $module) {
    $function = $module . '_token_list';
    foreach ($types as $type) {
      $result = $function($type);
      if (is_array($result)) {
        foreach ($result as $category => $tokens) {
          foreach ($tokens as $token => $title) {

            // Automatically append a raw token warning.
            if (substr($token, -4) === '-raw' && strpos($title, t('raw user input')) === FALSE && strpos($title, '1269441371') === FALSE) {
              $title .= ' <em>' . t('Warning: Token value contains raw user input.') . '</em>';
            }
            $return[$category][$token] = $title;
          }
        }
      }
    }
  }
  return $return;
}

/**
 * A helper function that transforms all the elements of an
 * array. Used to change the delimiter style from brackets to
 * percent symbols etc.
 *
 * @param tokens
 *    The array of tokens keys with no delimiting characters
 * @param leading
 *    Character(s) to prepend to the token key before searching for
 *    matches. Defaults to an open-bracket.
 * @param trailing
 *    Character(s) to append to the token key before searching for
 *    matches. Defaults to a close-bracket.
 *  @return
 *    The array of token keys, each wrapped in the specified
 *    delimiter style.
 */
function token_prepare_tokens($tokens = array(), $leading = TOKEN_PREFIX, $trailing = TOKEN_SUFFIX) {
  foreach ($tokens as $key => $value) {
    $tokens[$key] = $leading . $value . $trailing;
  }
  return $tokens;
}

// Internal utility function used for static caching. There are
// almost certainly better ways to do this, but for the moment it's
// sufficient.
function _token_get_id($type = 'global', $object = NULL) {
  if (!isset($object)) {
    return "default";
  }
  switch ($type) {
    case 'node':
      return isset($object->nid) ? $object->nid : 0;
    case 'comment':
      return isset($object->cid) ? $object->cid : 0;
    case 'user':
      return isset($object->uid) ? $object->uid : 0;
    case 'taxonomy':
      return isset($object->tid) ? $object->tid : 0;
    default:
      return crc32(serialize($object));
  }
}

/**
 * Build a list of common date tokens for use in hook_token_list().
 *
 * @param $description
 */
function token_get_date_token_info($description, $token_prefix = '') {
  $tokens[$token_prefix . 'small'] = t("!description date in 'small' format. <em>(06/07/2010 - 23:28)</em>", array(
    '!description' => $description,
  ));
  $tokens[$token_prefix . 'yyyy'] = t("!description year (four digit)", array(
    '!description' => $description,
  ));
  $tokens[$token_prefix . 'yy'] = t("!description year (two digit)", array(
    '!description' => $description,
  ));
  $tokens[$token_prefix . 'month'] = t("!description month (full word)", array(
    '!description' => $description,
  ));
  $tokens[$token_prefix . 'mon'] = t("!description month (abbreviated)", array(
    '!description' => $description,
  ));
  $tokens[$token_prefix . 'mm'] = t("!description month (two digits with leading zeros)", array(
    '!description' => $description,
  ));
  $tokens[$token_prefix . 'm'] = t("!description month (one or two digits without leading zeros)", array(
    '!description' => $description,
  ));
  $tokens[$token_prefix . 'ww'] = t("!description week (two digits with leading zeros)", array(
    '!description' => $description,
  ));
  if (version_compare(PHP_VERSION, '5.1.0', '>=')) {
    $tokens[$token_prefix . 'date'] = t("!description date (numeric representation of the day of the week)", array(
      '!description' => $description,
    ));
  }
  $tokens[$token_prefix . 'day'] = t("!description day (full word)", array(
    '!description' => $description,
  ));
  $tokens[$token_prefix . 'ddd'] = t("!description day (abbreviation)", array(
    '!description' => $description,
  ));
  $tokens[$token_prefix . 'dd'] = t("!description day (two digits with leading zeros)", array(
    '!description' => $description,
  ));
  $tokens[$token_prefix . 'd'] = t("!description day (one or two digits without leading zeros)", array(
    '!description' => $description,
  ));
  $tokens[$token_prefix . 'raw'] = t("!description in UNIX timestamp format (1269441371)", array(
    '!description' => $description,
  ));
  $tokens[$token_prefix . 'since'] = t("!description in 'time-since' format. (40 years 3 months)", array(
    '!description' => $description,
  ));
  return $tokens;
}

/**
 * Build a list of common date tokens for use in hook_token_values().
 *
 * @param $description
 */
function token_get_date_token_values($timestamp = NULL, $token_prefix = '') {
  $tokens = array();
  $time = time();
  if (!isset($timestamp)) {
    $timestamp = $time;
  }
  $timezone = variable_get('date_default_timezone', 0);
  $formats = array(
    'yyyy' => 'Y',
    'yy' => 'y',
    'month' => 'F',
    'mon' => 'M',
    'mm' => 'm',
    'm' => 'n',
    'ww' => 'W',
    //'date' => 'N', // Provided later since it is PHP 5.1 only.
    'day' => 'l',
    'ddd' => 'D',
    'dd' => 'd',
    'd' => 'j',
  );
  foreach ($formats as $token => $format) {
    $tokens[$token_prefix . $token] = format_date($timestamp, 'custom', $format, $timezone);
  }
  $tokens[$token_prefix . 'small'] = format_date($timestamp, 'small', '', $timezone);
  $tokens[$token_prefix . 'raw'] = $timestamp;
  $tokens[$token_prefix . 'since'] = format_interval($time - $timestamp);
  $timestamp += $timezone;
  if (version_compare(PHP_VERSION, '5.1.0', '>=')) {
    $tokens[$token_prefix . 'date'] = gmdate('N', $timestamp);
  }
  return $tokens;
}

/**
 * Validate an tokens in raw text based on possible contexts.
 *
 * @param $text
 *   A string with the raw text containing the raw tokens.
 * @param $valid_types
 *   An array of token types to validage against.
 * @param leading
 *    Character(s) to prepend to the token key before searching for
 *    matches. Defaults to an open-bracket.
 * @param trailing
 *    Character(s) to append to the token key before searching for
 *    matches. Defaults to a close-bracket.
 * @return
 *   An array with the invalid tokens in their original raw forms.
 */
function token_get_invalid_tokens_by_context($text, $valid_types = array(), $leading = TOKEN_PREFIX, $trailing = TOKEN_SUFFIX) {

  // Add the token types that are always valid in global context.
  $valid_types[] = 'global';
  $invalid_tokens = array();
  $valid_tokens = token_get_list($valid_types);
  foreach (token_scan($text, $leading, $trailing) as $token) {
    $found = FALSE;
    foreach ($valid_tokens as $category => $tokens) {
      if (isset($tokens[$token])) {
        $found = TRUE;
        break;
      }
      elseif (preg_match('/^(.*[_-])\\d+$/', $token, $result)) {

        // Some modules use a 'wildcard' token, e.g. Location.module defines
        // [location-country_N] but users actually use [location-country_0].
        // If the token has a digit on the end and there is a 'wildcard'
        // token definition, we should allow it.
        if (isset($tokens[$result[1] . 'N']) || isset($tokens[$result[1] . '?'])) {
          $found = TRUE;
          break;
        }
      }
    }
    if (!$found) {
      $invalid_tokens[] = $token;
    }
  }
  array_unique($invalid_tokens);
  $invalid_tokens = token_prepare_tokens($invalid_tokens, $leading, $trailing);
  return $invalid_tokens;
}

/**
 * Build a list of all token-like patterns that appear in the text.
 *
 * @param $text
 *   The text to be scanned for possible tokens.
 * @param leading
 *    Character(s) to prepend to the token key before searching for
 *    matches. Defaults to an open-bracket.
 * @param trailing
 *    Character(s) to append to the token key before searching for
 *    matches. Defaults to a close-bracket.
 * @return
 *   An array of discovered tokens.
 */
function token_scan($text, $leading = TOKEN_PREFIX, $trailing = TOKEN_SUFFIX) {

  // Matches tokens with the following pattern: [$token]
  $leading = preg_quote($leading, '/');
  $trailing = preg_quote($trailing, '/');
  preg_match_all("/{$leading}([^\\s]+?){$trailing}/", $text, $matches);
  return $matches[1];
}

/**
 * Validate a form element that should have tokens in it.
 *
 * Form elements that want to add this validation should have the #token_types
 * parameter defined.
 *
 * For example:
 * @code
 * $form['my_node_text_element'] = array(
 *   '#type' => 'textfield',
 *   '#title' => t('Some text to token-ize that has a node context.'),
 *   '#default_value' => 'The title of this node is [node:title].',
 *   '#validate' => array('token_element_validate_token_context' => array()),
 *   '#token_types' => array('node'),
 * );
 * @endcode
 */
function token_element_validate_token_context(&$element) {
  $value = isset($element['#value']) ? $element['#value'] : $element['#default_value'];
  $invalid_tokens = token_get_invalid_tokens_by_context($value, $element['#token_types']);
  if ($invalid_tokens) {
    form_error($element, t('The %element-title is using the following invalid tokens: @invalid-tokens.', array(
      '%element-title' => $element['#title'],
      '@invalid-tokens' => implode(', ', $invalid_tokens),
    )));
  }
  return $element;
}

/**
 * Find tokens that have been declared twice by different modules.
 */
function token_find_duplicate_tokens() {
  token_include();
  $all_tokens = array();
  foreach (module_implements('token_list') as $module) {
    $module_tokens = module_invoke($module, 'token_list', 'all');
    if (in_array($module, array(
      'node',
      'taxonomy',
      'comment',
      'user',
    ))) {
      $module = 'token';
    }
    foreach ($module_tokens as $type => $tokens) {
      foreach (array_keys($tokens) as $token) {
        $all_tokens[$type . ':' . $token][] = $module;
      }
    }
  }
  foreach ($all_tokens as $token => $modules) {
    if (count($modules) < 2) {
      unset($all_tokens[$token]);
    }
  }
  return $all_tokens;
}

Functions

Namesort descending Description
theme_token_help For a given context, builds a formatted list of tokens and descriptions of their replacement values.
token_element_validate_token_context Validate a form element that should have tokens in it.
token_find_duplicate_tokens Find tokens that have been declared twice by different modules.
token_get_date_token_info Build a list of common date tokens for use in hook_token_list().
token_get_date_token_values Build a list of common date tokens for use in hook_token_values().
token_get_invalid_tokens_by_context Validate an tokens in raw text based on possible contexts.
token_get_list A helper function that retrieves all currently exposed tokens, and merges them recursively. This is only necessary when building the token listing -- during actual value replacement, only tokens in a particular domain are requested and a normal…
token_get_values Return a list of valid substitution tokens and their values for the specified type.
token_help Implements hook_help().
token_include General function to include the files that token relies on for the real work.
token_prepare_tokens A helper function that transforms all the elements of an array. Used to change the delimiter style from brackets to percent symbols etc.
token_replace Return the value of $original, with all instances of placeholder tokens replaced by their proper values. To replace multiple types at once see token_replace_multiple().
token_replace_multiple Return the value of $original, with all instances of placeholder tokens replaced by their proper values. Contrary to token_replace(), this function supports replacing multiple types.
token_scan Build a list of all token-like patterns that appear in the text.
token_token_list Sample implementation of hook_token_list(). Documents the individual tokens handled by your module.
token_token_values Sample implementation of hook_token_values().
_token_get_id
_token_replace_tokens

Constants

Namesort descending Description
TOKEN_PREFIX The default token prefix string.
TOKEN_SUFFIX The default token suffix string.