Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.815 diff -u -p -r1.815 common.inc --- includes/common.inc 1 Nov 2008 21:21:34 -0000 1.815 +++ includes/common.inc 5 Nov 2008 01:41:19 -0000 @@ -2656,6 +2656,7 @@ function _drupal_bootstrap_full() { require_once DRUPAL_ROOT . '/includes/form.inc'; require_once DRUPAL_ROOT . '/includes/mail.inc'; require_once DRUPAL_ROOT . '/includes/actions.inc'; + require_once DRUPAL_ROOT . '/includes/token.inc'; // Set the Drupal custom error handler. set_error_handler('_drupal_error_handler'); set_exception_handler('_drupal_exception_handler'); Index: includes/token.inc =================================================================== RCS file: includes/token.inc diff -N includes/token.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ includes/token.inc 5 Nov 2008 01:41:19 -0000 @@ -0,0 +1,268 @@ +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 mutiple 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. + * @return The modified version of $original, with all substitutions + * made. + **/ +function token_replace_multiple($original, $types = array('global' => NULL), $leading = '[', $trailing = ']') { + $full = new stdClass(); + $full->tokens = $full->values = array(); + foreach ($types as $type => $object) { + $temp = token_get_values($type, $object); + $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 + * Optional. If TRUE, the internal token cache is cleared. + * @param test_context + * Optional (only used for testing purposes). If provided, the internally + * cached tokens and recursion depth are replaced with those from the + * test_context. + * @return + * A keyed array containing the substitution tokens and the substition + * values for the passed-in type and object. + */ +function token_get_values($type = 'global', $object = NULL, $flush = FALSE, $test_context = NULL) { + static $tokens; + static $depth; + + // If this function is being called during a unit test, set up the static variables + // according to the needs of the test. This would be cleaner if implemented as + // a class. + if (isset($test_context)) { + $tokens = $test_context->tokens; + $depth = $test_context->depth; + } + + // 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(); + } + + // 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 (!isset($depth)) { + $depth = 1; + } + elseif ($depth = 1) { + $depth++; + } + else { + // 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; + } + + $id = _token_get_id($type, $object); + if (isset($tokens[$type][$id])) { + $tmp_tokens = $tokens[$type][$id]; + } + else { + $tmp_tokens = module_invoke_all('token_values', $type, $object); + $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); + + $depth--; + + 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 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. + * @return + * The array of usable tokens and their descriptions, organized by + * token type. + */ +function token_get_list($type = 'all') { + $return = array(); + foreach (module_implements('token_list') as $module) { + $function = $module .'_token_list'; + $result = $function($type); + if (is_array($result)) { + foreach ($result as $category => $tokens) { + foreach ($tokens as $token => $title) { + $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 chacaters + * @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 = '[', $trailing = ']') { + if (!isset($tokens)) { + $tokens = array(); + } + 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 $object->nid; + case 'comment': + return $object->cid; + case 'user': + return $object->uid; + default: + return crc32(serialize($object)); + } +} + +/** + * Check if mid/path is present in the menu. + * + * @param $in + * Numeric input is treated as a menu-id, strings as src-paths. + * @return + * An existing mid, or 0 if none found. + */ +function token_menu_get_mid($in) { + global $_menu; + + if (!is_numeric($in)) { + if (isset($_menu['path index'][$in])) { + $mid = $_menu['path index'][$in]; + } + else { + $mid = 0; + } + } + else if (!isset($_menu['visible'][$in])) { + $mid = 0; + } + + // temporary paths would break much of this module + if ($mid < 0) $mid = 0; + + return $mid; +} Index: modules/book/book.module =================================================================== RCS file: /cvs/drupal/drupal/modules/book/book.module,v retrieving revision 1.473 diff -u -p -r1.473 book.module --- modules/book/book.module 29 Oct 2008 10:08:51 -0000 1.473 +++ modules/book/book.module 5 Nov 2008 01:41:19 -0000 @@ -1169,3 +1169,57 @@ function book_menu_subtree_data($item) { return $tree[$cid]; } + +/** + * Implementation of hook_token_values() + */ +function book_token_values($type, $object = NULL) { + if ($type == 'node') { + $node = $object; + + $tokens = array(); + if (isset($node->parent)) { + $path = book_location($node); + $tokens['book'] = check_plain($path[0]->title); + $tokens['book-raw'] = $path[0]->title; + $tokens['book_id'] = $node->parent; + + $bookhierarchy = book_location($node); + $bookpath = ''; + $bookpath_raw = ''; + foreach ($bookhierarchy as $bookelement) { + if ($bookpath == '') { + $bookpath = check_plain($bookelement->title); + $bookpath_raw = $bookelement->title; + } + else { + $bookpath = $bookpath .'/'. check_plain($bookelement->title); + $bookpath_raw = $bookpath_raw . '/' . $bookelement->title; + } + } + $tokens['bookpath'] = $bookpath; + $tokens['bookpath-raw'] = $bookpath_raw; + } + else { + $tokens['book'] = ''; + $tokens['book_id'] = ''; + $tokens['bookpath'] = ''; + $tokens['bookpath-raw'] = ''; + } + return $tokens; + } +} + +/** + * Implementation of hook_token_list() + */ +function book_token_list($type) { + if ($type == 'node' || $type == 'all') { + $list['book']['book'] = t("The title of the node's book parent."); + $list['book']['book-raw'] = t("The unfiltered title of the node's book parent. WARNING - raw user input."); + $list['book']['book_id'] = t("The id of the node's book parent."); + $list['book']['bookpath'] = t("The titles of all parents in the node's book hierarchy."); + $list['book']['bookpath-raw'] = t("The unfiltered titles of all parents in the node's book hierarchy. WARNING - raw user input."); + return $list; + } +} Index: modules/comment/comment.module =================================================================== RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v retrieving revision 1.660 diff -u -p -r1.660 comment.module --- modules/comment/comment.module 1 Nov 2008 19:51:06 -0000 1.660 +++ modules/comment/comment.module 5 Nov 2008 01:41:19 -0000 @@ -2020,3 +2020,79 @@ function comment_ranking() { ), ); } + +/** + * Implementation of hook_token_values() + */ +function comment_token_values($type, $object = NULL) { + $values = array(); + switch ($type) { + case 'comment': + + // Cast to an object just in case fussy drupal gave us an array + $comment = (object)$object; + + $values['comment-nid'] = $comment->nid; + $values['comment-title'] = check_plain($comment->subject); + $values['comment-body'] = check_markup($comment->comment, $comment->format); + $values['comment-author-name'] = check_plain($comment->name); + $values['comment-author-uid'] = $comment->uid; + + // Raw counterparts of user supplied data. + $values['comment-title-raw'] = $comment->subject; + $values['comment-body-raw'] = $comment->comment; + $values['comment-author-name-raw'] = $comment->name; + + // Included in case a consuming module wants to format the body + $values['comment-body-format'] = $comment->format; + + + $values['comment-yyyy'] = date('Y', $comment->timestamp); + $values['comment-yy'] = date('y', $comment->timestamp); + $values['comment-month'] = date('F', $comment->timestamp); + $values['comment-mon'] = date('M', $comment->timestamp); + $values['comment-mm'] = date('m', $comment->timestamp); + $values['comment-m'] = date('n', $comment->timestamp); + $values['comment-ww'] = date('W', $comment->timestamp); + $values['comment-date'] = date('N', $comment->timestamp); + $values['comment-day'] = date('l', $comment->timestamp); + $values['comment-ddd'] = date('D', $comment->timestamp); + $values['comment-dd'] = date('d', $comment->timestamp); + $values['comment-d'] = date('j', $comment->timestamp); + break; + } + + return $values; +} + +/** + * Implementation of hook_token_list() + */ +function comment_token_list($type = 'all') { + if ($type == 'comment' || $type == 'all') { + $tokens['comment']['comment-nid'] = t('Comment ID'); + $tokens['comment']['comment-title'] = t('Comment title'); + $tokens['comment']['comment-title-raw'] = t('Comment title. WARNING - raw user input.'); + $tokens['comment']['comment-body'] = t('Comment body'); + $tokens['comment']['comment-body-raw'] = t('Comment body. WARNING - raw user input.'); + + $tokens['comment']['comment-author-uid'] = t("Comment author's user id"); + $tokens['comment']['comment-author-name'] = t("Comment author's user name"); + $tokens['comment']['comment-author-name-raw'] = t("Comment author's user name. WARNING - raw user input."); + + $tokens['comment']['comment-yyyy'] = t("Comment creation year (four digit)"); + $tokens['comment']['comment-yy'] = t("Comment creation year (two digit)"); + $tokens['comment']['comment-month'] = t("Comment creation month (full word)"); + $tokens['comment']['comment-mon'] = t("Comment creation month (abbreviated)"); + $tokens['comment']['comment-mm'] = t("Comment creation month (two digit, zero padded)"); + $tokens['comment']['comment-m'] = t("Comment creation month (one or two digit)"); + $tokens['comment']['comment-ww'] = t("Comment creation week (two digit)"); + $tokens['comment']['comment-date'] = t("Comment creation date (day of month)"); + $tokens['comment']['comment-day'] = t("Comment creation day (full word)"); + $tokens['comment']['comment-ddd'] = t("Comment creation day (abbreviation)"); + $tokens['comment']['comment-dd'] = t("Comment creation day (two digit, zero-padded)"); + $tokens['comment']['comment-d'] = t("Comment creation day (one or two digit)"); + + return $tokens; + } +} Index: modules/node/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.module,v retrieving revision 1.993 diff -u -p -r1.993 node.module --- modules/node/node.module 3 Nov 2008 05:55:56 -0000 1.993 +++ modules/node/node.module 5 Nov 2008 01:41:20 -0000 @@ -2925,3 +2925,129 @@ function node_list_permissions($type) { return $perms; } + +/** + * Implementation of hook_token_values() + */ +function node_token_values($type, $object = NULL) { + $values = array(); + switch ($type) { + case 'node': + $node = $object; + + $values['nid'] = $node->nid; + $values['type'] = $node->type; + $values['type-name'] = node_get_types('name', $node->type); + $values['title'] = check_plain($node->title); + $values['title-raw'] = $node->title; + + $values['author-uid'] = $node->uid; + $values['author-name'] = check_plain($node->name); + $values['author-name-raw'] = $node->name; + + $values['yyyy'] = date('Y', $node->created); + $values['yy'] = date('y', $node->created); + $values['month'] = date('F', $node->created); + $values['mon'] = date('M', $node->created); + $values['mm'] = date('m', $node->created); + $values['m'] = date('n', $node->created); + $values['ww'] = date('W', $node->created); + $values['date'] = date('N', $node->created); + $values['day'] = date('l', $node->created); + $values['ddd'] = date('D', $node->created); + $values['dd'] = date('d', $node->created); + $values['d'] = date('j', $node->created); + + $values['mod-yyyy'] = date('Y', $node->changed); + $values['mod-yy'] = date('y', $node->changed); + $values['mod-month'] = date('F', $node->changed); + $values['mod-mon'] = date('M', $node->changed); + $values['mod-mm'] = date('m', $node->changed); + $values['mod-m'] = date('n', $node->changed); + $values['mod-ww'] = date('W', $node->changed); + $values['mod-date'] = date('N', $node->changed); + $values['mod-day'] = date('l', $node->changed); + $values['mod-ddd'] = date('D', $node->changed); + $values['mod-dd'] = date('d', $node->changed); + $values['mod-d'] = date('j', $node->changed); + + // Now get the menu related information. + global $_menu; + $trail = array(); + $trail_raw = array(); + $original_mid = token_menu_get_mid('node/'.$node->nid); + $mid = $original_mid; + while ($mid && $_menu['visible'][$mid]) { + array_unshift($trail, check_plain($_menu['visible'][$mid]['title'])); + $mid = $_menu['visible'][$mid]['pid']; + } + + // One more time, unfiltered + $mid = $original_mid; + while ($mid && $_menu['visible'][$mid] && $_menu['visible'][$mid]['pid'] != 0) { + array_unshift($trail_raw, $_menu['visible'][$mid]['title']); + $mid = $_menu['visible'][$mid]['pid']; + } + + if (isset($trail)) { + $values['menupath'] = implode('/', $trail); + $values['menupath-raw'] = implode('/', $trail_raw); + $values['menu'] = array_shift($trail); + } + else { + $values['menu'] = ''; + $values['menupath'] = ''; + $values['menupath-raw'] = ''; + } + + if (!isset($values['term'])) { + $values['term'] = ''; + $values['term-raw'] = ''; + $values['term-id'] = ''; + $values['vocab'] = ''; + $values['vocab-raw'] = ''; + $values['vocab-id'] = ''; + } + break; + } + + return $values; +} + +/** + * Implementation of hook_token_list() + */ +function node_token_list($type = 'all') { + if ($type == 'node' || $type == 'all') { + $tokens['node']['nid'] = t('Node ID'); + $tokens['node']['type'] = t('Node type'); + $tokens['node']['type-name'] = t('Node type (user-friendly version)'); + $tokens['node']['title'] = t('Node title'); + $tokens['node']['title-raw'] = t('Unfiltered node title. WARNING - raw user input.'); + + $tokens['node']['author-uid'] = t("Node author's user id"); + $tokens['node']['author-name'] = t("Node author's user name"); + $tokens['node']['author-name-raw'] = t("Node author's user name. WARNING - raw user input."); + + + $tokens['node']['yyyy'] = t("Node creation year (four digit)"); + $tokens['node']['yy'] = t("Node creation year (two digit)"); + $tokens['node']['month'] = t("Node creation month (full word)"); + $tokens['node']['mon'] = t("Node creation month (abbreviated)"); + $tokens['node']['mm'] = t("Node creation month (two digit, zero padded)"); + $tokens['node']['m'] = t("Node creation month (one or two digit)"); + $tokens['node']['ww'] = t("Node creation week (two digit)"); + $tokens['node']['date'] = t("Node creation date (day of month)"); + $tokens['node']['day'] = t("Node creation day (full word)"); + $tokens['node']['ddd'] = t("Node creation day (abbreviation)"); + $tokens['node']['dd'] = t("Node creation day (two digit, zero-padded)"); + $tokens['node']['d'] = t("Node creation day (one or two digit)"); + $tokens['node']['mod-????'] = t('All tokens for node creation dates can also be used with with the "mod-" prefix; doing so will use the modification date rather than the creation date.'); + + $tokens['node']['menu'] = t("The name of the menu the node belongs to."); + $tokens['node']['menupath'] = t("The menu path (as reflected in the breadcrumb), not including Home or [menu]. Separated by /."); + $tokens['node']['menupath-raw'] = t("The unfiltered menu path (as reflected in the breadcrumb), not including Home or [menu]. Separated by /. WARNING - raw user input."); + + return $tokens; + } +} Index: modules/simpletest/tests/token.test =================================================================== RCS file: modules/simpletest/tests/token.test diff -N modules/simpletest/tests/token.test --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/simpletest/tests/token.test 5 Nov 2008 01:41:20 -0000 @@ -0,0 +1,248 @@ + t('Token API'), + 'description' => t('Tests for the token API'), + 'group' => t('Token') + ); + } + + /** + * Verifies the results of token_prepare_tokens. This function takes an + * array of tokens and creates a copy in which the values for the tokens + * have a prefix and suffix. + * + * @param array $orig_tokens The original token array passed into token_prepare_tokens. + * @param array $modified_tokens The array resulting from a call to token_prepare_tokens. + * @param string $leading The string representing the value prefix. + * @param string $trailing The string representing the value suffix. + * @return object that indicates whether the modified tokens array is correct + * based on the original tokens and leading and trailing strings. + */ + function verifyPreparedTokens($orig_tokens = array(), $modified_tokens, $leading = '[', $trailing = ']') { + if (!isset($orig_tokens)) { + $orig_tokens = array(); + } + $result = new stdClass(); + $result->verified = FALSE; + if (sizeof($orig_tokens) != sizeof($modified_tokens)) { + $result->message = 'Size of the arrays do not match.'; + return $result; + } + foreach ($orig_tokens as $key => $value) { + $new_value = $leading . $value . $trailing; + if ($modified_tokens[$key] != $new_value) { + $result->message = 'New value for ' . $value . ' should be ' .$new_value + . ' but actually was ' .$modified_tokens[$key]; + return $result; + } + } + $result->verified = TRUE; + $result->message = 'Resulting array is correct.'; + return $result; + } + + /** + * Tests that a simple set of tokens is "prepared" properly by the + * token_prepare_tokens function. + */ + function testTokenPrepareTokens() { + $tokens = array('user' => 'tester', 'password' => 'test123', 'hobby' => 'welding'); + $modifiedTokens = token_prepare_tokens($tokens); + $result = $this->verifyPreparedTokens($tokens, $modifiedTokens); + $this->assertTrue($result->verified, $result->message); + } + + /** + * Tests the token_prepare_tokens function when passed different leading and + * trailing strings. + */ + function testTokenPrepareTokensLeadingAndTrailing() { + $tokens = array('user' => 'tester', 'password' => 'test123', 'hobby' => 'welding'); + $leading = '%'; + $trailing = '>'; + $modifiedTokens = token_prepare_tokens($tokens, $leading, $trailing); + $result = $this->verifyPreparedTokens($tokens, $modifiedTokens, $leading, $trailing); + $this->assertTrue($result->verified, $result->message); + } + + /** + * Tests the token_prepare_tokens function when passed NULL for the array. + */ + function testTokenPrepareTokensNullTokenList() { + $tokens = NULL; + $modifiedTokens = token_prepare_tokens($tokens); + $result = $this->verifyPreparedTokens($tokens, $modifiedTokens); + $this->assertTrue($result->verified, $result->message); + } + + /** + * Returns a set of tokens that will be used as the default tokens for all + * tests. + * + * @return An array of tokens (key/value pairs). + */ + function getDefaultTokens() { + $tokens = array('site-url' => 'http://localhost', 'site-name' => 'Drupal', + 'site-slogan' => '', 'site-email' => '', + 'site-date' => 'Tue, 11/04/2008 - 20:29', 'user-name' => 'admin', + 'user-id' => '1', 'user-mail' => 'admin@example.com'); + return $tokens; + } + + /** + * Verifies that the tokens returned from token_get_values include + * all of the specified expected tokens. For example, this can be + * used to verify that all of the global/default tokens are included. + * + * @param obj $tokens the tokens The object returned from token_get_values + * @param array $expected_tokens Contains the expected tokens (key/value pair). + * @return An object that contains the result of the check (->verified = + * TRUE if successful; FALSE otherwise) and an associated message (->message) + * that can be passed to $this->assertTrue(). + */ + function checkTokensAreIncluded($tokens, $expected_tokens = NULL) { + $result = new stdClass(); + $result->verified = FALSE; + if (!isset($expected_tokens)) { + $expected_tokens = $this->getDefaultTokens(); + } + $size = sizeof($tokens->tokens); + for ($i = 0; $i < $size; $i++) { + $key = $tokens->tokens[$i]; + $value = $tokens->values[$i]; + if (!isset($expected_tokens[$key])) { + // This does not represent an expected token. + continue; + } + if ($expected_tokens[$key] != $value) { + $result->message = 'Value for token ' .$key .' should be ' .$value . + ', not ' .$expected_tokens[$key]; + return $result; + } + // Remove the matching item from the expected tokens array so a final check + // for completeness can efficiently be performed. + unset($expected_tokens[$key]); + } + // Final check that all of the expected tokens were included. + if (sizeof($expected_tokens) > 0) { + $result->message = 'Some expected tokens were missing: [' + .implode(',', array_keys($expected_tokens)) .']'; + return $result; + } + $result->verified = TRUE; + $result->message = 'Resulting tokens contain the expected set.'; + return $result; + } + + /** + * Creates a test context that holds tokens that can be used to verify + * the API is working correctly. + * + * @return the test context that contains the global/default tokens. + */ + function getTestContext() { + $default_tokens = $this->getDefaultTokens(); + $testContext = new stdClass(); + $testContext->tokens = array('global' => array('default' => $default_tokens)); + $testContext->depth = 0; + return $testContext; + } + + /** + * Adds tokens to the test context. + */ + function addTokens(&$test_context, $type, $id, $new_tokens) { + if (!isset($test_context->tokens[$type])) { + $test_context->tokens[$type] = array(); + } + if (!isset($test_context->tokens[$type][$id])) { + $test_context->tokens[$type][$id] = array(); + } + $test_context->tokens[$type][$id] = array_merge($test_context->tokens[$type][$id], $new_tokens); + } + + /** + * Verify that the global, default tokens are returned from token_get_values. + */ + function testTokenGetValues() { + $test_context = $this->getTestContext(); + $this->addTokens($test_context, 'test', 'help', + array('help_text' => 'Here is some help text', 'help_status' => 'Success')); + $tokens = token_get_values('global', NULL, FALSE, $test_context); + $result = $this->checkTokensAreIncluded($tokens); + $this->assertTrue($result->verified, $result->message); + } + + /** + * Verify that the global tokens and the requested tokens are returned + * from token_get_values. + */ + function testTokenGetValuesNonglobalType() { + $nondefault_tokens = array('test_text' => 'Here is some test text', + 'status' => 'Success'); + $test_context = $this->getTestContext(); + $this->addTokens($test_context, 'test', 'default', $nondefault_tokens); + $tokens = token_get_values('test', NULL, FALSE, $test_context); + $result = $this->checkTokensAreIncluded($tokens); + $this->assertTrue($result->verified, $result->message); + + // Be sure the non-default tokens are also present + $result = $this->checkTokensAreIncluded($tokens, $nondefault_tokens); + $this->assertTrue($result->verified, $result->message); + } + + /** + * Make sure the token_replace function is able to replace tokens in a string + * from one type ('global' + 1 other). + */ + function testTokenReplace() { + $default_tokens = $this->getDefaultTokens(); + $test_tokens = array('test_text' => 'Here is some test text', 'status' => 'Success'); + $sample = '[status], [test_text], [site-url]'; + $test_context = $this->getTestContext(); + $this->addTokens($test_context, 'test', 'default', $test_tokens); + + // Need to initialize the cached tokens: + $tokens = token_get_values('test', NULL, FALSE, $test_context); + + // Calculate the expected result + $expected_result = implode(', ', array($test_tokens['status'], + $test_tokens['test_text'], $default_tokens['site-url'])); + $result = token_replace($sample, 'test'); + $this->assertTrue($result == $expected_result, 'Expected result: ' + .$expected_result .', actual: ' .$result); + } + + /** + * Make sure the token_replace_multiple function is able to replace tokens + * of multiple types ('global' + at least 2 others). + */ + function testTokenReplaceMultiple() { + $default_tokens = $this->getDefaultTokens(); + $test_tokens = array('test_text' => 'Here is some test text', 'status' => 'Success'); + $other_tokens = array('other_text' => 'Other text here', 'other_mode' => 'Blinking'); + $sample = '[other_mode], [status], [test_text], [other_text], [site-url]'; + $test_context = $this->getTestContext(); + $this->addTokens($test_context, 'test', 'default', $test_tokens); + $this->addTokens($test_context, 'other', 'default', $other_tokens); + + // Need to initialize the cached tokens: + $tokens = token_get_values('test', NULL, FALSE, $test_context); + + // Calculate the expected result + $expected_result = implode(', ', array($other_tokens['other_mode'], $test_tokens['status'], + $test_tokens['test_text'], $other_tokens['other_text'], $default_tokens['site-url'])); + $result = token_replace_multiple($sample, array('test' => NULL, 'other' => NULL, 'global' => NULL)); + $this->assertTrue($result == $expected_result, 'Expected result: ' .$expected_result .', actual: ' .$result); + } +} Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.635 diff -u -p -r1.635 system.module --- modules/system/system.module 31 Oct 2008 02:18:22 -0000 1.635 +++ modules/system/system.module 5 Nov 2008 01:41:20 -0000 @@ -2151,3 +2151,34 @@ function theme_meta_generator_header($ve function system_image_toolkits() { return array('gd'); } + +/** + * Implementation of hook_token_values() + */ +function system_token_values($type, $object = NULL) { + $values = array(); + switch ($type) { + case 'global': + global $base_url; + $values['site-url'] = $base_url; + $values['site-name'] = variable_get('site_name', t('Drupal')); + $values['site-slogan'] = variable_get('site_slogan', ''); + $values['site-mail'] = variable_get('site_mail', ''); + $values['site-date'] = format_date(time(), 'short', '', variable_get('date_default_timezone', 0)); + break; + } + return $values; +} + +/** + * Implementation of hook_token_list() + */ +function system_token_list($type = 'all') { + $tokens = array(); + $tokens['global']['site-url'] = t('The url of the current Drupal website.'); + $tokens['global']['site-name'] = t('The name of the current Drupal website.'); + $tokens['global']['site-slogan'] = t('The slogan of the current Drupal website.'); + $tokens['global']['site-mail'] = t('The contact email address for the current Drupal website.'); + $tokens['global']['site-date'] = t("The current date on the site's server."); + return $tokens; +} \ No newline at end of file Index: modules/taxonomy/taxonomy.module =================================================================== RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v retrieving revision 1.436 diff -u -p -r1.436 taxonomy.module --- modules/taxonomy/taxonomy.module 2 Nov 2008 17:46:47 -0000 1.436 +++ modules/taxonomy/taxonomy.module 5 Nov 2008 01:41:20 -0000 @@ -1404,3 +1404,87 @@ function taxonomy_hook_info() { ), ); } + +/** + * Implementation of hook_token_values() + */ +function taxonomy_token_values($type, $object = NULL) { + $values = array(); + switch ($type) { + case 'taxonomy': + $category = $object; + + $vid = $category->vid; + $vocabulary = taxonomy_vocabulary_load($vid); + $values['vid'] = $vid; + $values['vocab'] = check_plain($vocabulary->name); + $values['cat'] = check_plain($category->name); + $values['tid'] = $category->tid; + $values['vocab-raw'] = $vocabulary->name; + $values['cat-raw'] = $category->name; + + break; + case 'node': + $node = $object; + // And now taxonomy, which is a bit more work. This code is adapted from + // pathauto's handling code; it's intended for compatability with it. + if (!empty($node->taxonomy) && is_array($node->taxonomy)) { + foreach ($node->taxonomy as $term) { + if ((object)$term) { + // With freetagging it's somewhat hard to get the tid, vid, name values + // Rather than duplicating taxonomy.module code here you should make sure your calling module + // has a weight of at least 1 which will run after taxonomy has saved the data which allows us to + // pull it out of the db here. + if (!isset($term->name) || !isset($term->tid)) { + $vid = db_result(db_query("SELECT t.vid FROM {term_node} r INNER JOIN {term_data} t ON r.tid = t.tid INNER JOIN {vocabulary} v ON t.vid = v.vid WHERE r.nid = %d ORDER BY v.weight, t.weight, t.name LIMIT 1", $object->nid)); + $term = db_fetch_object(db_query("SELECT t.tid, t.name FROM {term_data} t INNER JOIN {term_node} r ON r.tid = t.tid WHERE t.vid = %d AND r.nid = %d ORDER BY weight LIMIT 1", $vid, $object->nid)); + $term->vid = $vid; + } + + $values['term'] = check_plain($term->name); + $values['term-raw'] = $term->name; + $values['term-id'] = $term->tid; + $vid = $term->vid; + + if (!empty($vid)) { + $vocabulary = taxonomy_vocabulary_load($vid); + $values['vocab'] = check_plain($vocabulary->name); + $values['vocab-raw'] = $vocabulary->name; + $values['vocab-id'] = $vocabulary->name; + } + + break; + } + } + } + break; + } + return $values; +} + +/** + * Implementation of hook_token_list() + */ +function taxonomy_token_list($type = 'all') { + if ($type == 'taxonomy' || $type == 'all') { + $tokens['taxonomy']['tid'] = t("The id number of the category's parent vocabulary."); + $tokens['taxonomy']['vocab'] = t("The vocabulary that the page's first category belongs to."); + $tokens['taxonomy']['cat'] = t('The name of the category.'); + $tokens['taxonomy']['tid'] = t('The id number of the category.'); + $tokens['taxonomy']['vocab-raw'] = t("The unfiltered vocabulary that the page's first category belongs to. WARNING - raw user input."); + $tokens['taxonomy']['cat-raw'] = t('The unfiltered name of the category. WARNING - raw user input.'); + if ($type == 'node' || $type == 'all') { + $tokens['node']['term'] = t("Name of top taxonomy term"); + $tokens['node']['term-raw'] = t("Unfiltered name of top taxonomy term. WARNING - raw user input."); + $tokens['node']['term-id'] = t("ID of top taxonomy term"); + $tokens['node']['vocab'] = t("Name of top term's vocabulary"); + $tokens['node']['vocab-raw'] = t("Unfiltered name of top term's vocabulary. WARNING - raw user input."); + $tokens['node']['vocab-id'] = t("ID of top term's vocabulary"); + // Temporarily disabled -- see notes in node_token_values. + // TODO: I didn't see a relevant note in the original code. I'm tempted to + // remove this. + // $tokens['node']['catpath'] = t("Full taxonomy tree for the topmost term"); + } + return $tokens; + } +} Index: modules/user/user.module =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.module,v retrieving revision 1.930 diff -u -p -r1.930 user.module --- modules/user/user.module 26 Oct 2008 18:06:39 -0000 1.930 +++ modules/user/user.module 5 Nov 2008 01:41:20 -0000 @@ -2447,3 +2447,67 @@ function _user_forms(&$edit, $account, $ return empty($groups) ? FALSE : $groups; } +/** + * Implementation of hook_token_values() + */ +function user_token_values($type, $object = NULL) { + $values = array(); + switch ($type) { + case 'user': + if (isset($object)) { + $account = $object; + } + else { + global $user; + $account = user_load(array('uid' => $user->uid)); + } + + $values['user'] = $account->uid ? check_plain($account->name) : variable_get('anonymous', 'Anonymous'); + $values['user-raw'] = $account->uid ? $account->name : variable_get('anonymous', 'Anonymous'); + $values['uid'] = $account->uid; + $values['mail'] = $account->uid ? $account->mail : ''; + $values['reg-date'] = $account->uid ? format_date($account->created, 'short') : ''; + $values['reg-since'] = $account->uid ? format_interval($account->created) : ''; + $values['log-date'] = $account->uid ? format_date($account->access, 'short') : ''; + $values['log-since'] = $account->uid ? format_interval($account->access) : ''; + $values['date-in-tz'] = $account->uid ? format_date(time(), 'short', '', $account->timezone) : ''; + $values['account-url'] = $account->uid ? url("user/$account->uid") : ''; + $values['account-edit'] = $account->uid ? url("user/$account->uid/edit") : ''; + + break; + case 'global': + global $user; + $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 : ''; + break; + } + return $values; +} + +/** + * Implementation of hook_token_list() + */ +function user_token_list($type = 'all') { + $tokens = array(); + if ($type == 'user' || $type == 'all') { + $tokens['user']['user'] = t("User's name"); + $tokens['user']['user-raw'] = t("User's unfiltered name. WARNING - raw user input."); + $tokens['user']['uid'] = t("User's ID"); + $tokens['user']['mail'] = t("User's email address"); + + $tokens['user']['reg-date'] = t("User's registration date"); + $tokens['user']['reg-since'] = t("Days since the user registered"); + $tokens['user']['log-date'] = t("User's last login date"); + $tokens['user']['log-since'] = t("Days since the user's last login"); + $tokens['user']['date-in-tz'] = t("The current date in the user's timezone"); + $tokens['user']['account-url'] = t("The URL of the user's profile page."); + $tokens['user']['account-edit'] = t("The URL the user's account editing page."); + } + + $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.'); + + return $tokens; +}