token.inc 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. <?php
  2. /**
  3. * @file
  4. * Drupal placeholder/token replacement system.
  5. *
  6. * API functions for replacing placeholders in text with meaningful values.
  7. *
  8. * For example: When configuring automated emails, an administrator enters
  9. * standard text for the email. Variables like the title of a node and the date
  10. * the email was sent can be entered as placeholders like [node:title] and
  11. * [date:short]. When a Drupal module prepares to send the email, it can call
  12. * the token_replace() function, passing in the text. The token system will
  13. * scan the text for placeholder tokens, give other modules an opportunity to
  14. * replace them with meaningful text, then return the final product to the
  15. * original module.
  16. *
  17. * Tokens follow the form: [$type:$name], where $type is a general class of
  18. * tokens like 'node', 'user', or 'comment' and $name is the name of a given
  19. * placeholder. For example, [node:title] or [node:created:since].
  20. *
  21. * In addition to raw text containing placeholders, modules may pass in an array
  22. * of objects to be used when performing the replacement. The objects should be
  23. * keyed by the token type they correspond to. For example:
  24. *
  25. * @code
  26. * // Load a node and a user, then replace tokens in the text.
  27. * $text = 'On [date:short], [user:name] read [node:title].';
  28. * $node = node_load(1);
  29. * $user = user_load(1);
  30. *
  31. * // [date:...] tokens use the current date automatically.
  32. * $data = array('node' => $node, 'user' => $user);
  33. * return token_replace($text, $data);
  34. * @endcode
  35. *
  36. * Some tokens may be chained in the form of [$type:$pointer:$name], where $type
  37. * is a normal token type, $pointer is a reference to another token type, and
  38. * $name is the name of a given placeholder. For example, [node:author:mail]. In
  39. * that example, 'author' is a pointer to the 'user' account that created the
  40. * node, and 'mail' is a placeholder available for any 'user'.
  41. *
  42. * @see token_replace()
  43. * @see hook_tokens()
  44. * @see hook_token_info()
  45. */
  46. /**
  47. * Replaces all tokens in a given string with appropriate values.
  48. *
  49. * @param $text
  50. * A string potentially containing replaceable tokens.
  51. * @param $data
  52. * (optional) An array of keyed objects. For simple replacement scenarios
  53. * 'node', 'user', and others are common keys, with an accompanying node or
  54. * user object being the value. Some token types, like 'site', do not require
  55. * any explicit information from $data and can be replaced even if it is
  56. * empty.
  57. * @param $options
  58. * (optional) A keyed array of settings and flags to control the token
  59. * replacement process. Supported options are:
  60. * - language: A language object to be used when generating locale-sensitive
  61. * tokens.
  62. * - callback: A callback function that will be used to post-process the array
  63. * of token replacements after they are generated. For example, a module
  64. * using tokens in a text-only email might provide a callback to strip HTML
  65. * entities from token values before they are inserted into the final text.
  66. * - clear: A boolean flag indicating that tokens should be removed from the
  67. * final text if no replacement value can be generated.
  68. * - sanitize: A boolean flag indicating that tokens should be sanitized for
  69. * display to a web browser. Defaults to TRUE. Developers who set this
  70. * option to FALSE assume responsibility for running filter_xss(),
  71. * check_plain() or other appropriate scrubbing functions before displaying
  72. * data to users.
  73. *
  74. * @return
  75. * Text with tokens replaced.
  76. */
  77. function token_replace($text, array $data = array(), array $options = array()) {
  78. $text_tokens = token_scan($text);
  79. if (empty($text_tokens)) {
  80. return $text;
  81. }
  82. $replacements = array();
  83. foreach ($text_tokens as $type => $tokens) {
  84. $replacements += token_generate($type, $tokens, $data, $options);
  85. if (!empty($options['clear'])) {
  86. $replacements += array_fill_keys($tokens, '');
  87. }
  88. }
  89. // Optionally alter the list of replacement values.
  90. if (!empty($options['callback']) && function_exists($options['callback'])) {
  91. $function = $options['callback'];
  92. $function($replacements, $data, $options);
  93. }
  94. $tokens = array_keys($replacements);
  95. $values = array_values($replacements);
  96. return str_replace($tokens, $values, $text);
  97. }
  98. /**
  99. * Builds a list of all token-like patterns that appear in the text.
  100. *
  101. * @param $text
  102. * The text to be scanned for possible tokens.
  103. *
  104. * @return
  105. * An associative array of discovered tokens, grouped by type.
  106. */
  107. function token_scan($text) {
  108. // Matches tokens with the following pattern: [$type:$name]
  109. // $type and $name may not contain [ ] characters.
  110. // $type may not contain : or whitespace characters, but $name may.
  111. preg_match_all('/
  112. \[ # [ - pattern start
  113. ([^\s\[\]:]*) # match $type not containing whitespace : [ or ]
  114. : # : - separator
  115. ([^\[\]]*) # match $name not containing [ or ]
  116. \] # ] - pattern end
  117. /x', $text, $matches);
  118. $types = $matches[1];
  119. $tokens = $matches[2];
  120. // Iterate through the matches, building an associative array containing
  121. // $tokens grouped by $types, pointing to the version of the token found in
  122. // the source text. For example, $results['node']['title'] = '[node:title]';
  123. $results = array();
  124. for ($i = 0; $i < count($tokens); $i++) {
  125. $results[$types[$i]][$tokens[$i]] = $matches[0][$i];
  126. }
  127. return $results;
  128. }
  129. /**
  130. * Generates replacement values for a list of tokens.
  131. *
  132. * @param $type
  133. * The type of token being replaced. 'node', 'user', and 'date' are common.
  134. * @param $tokens
  135. * An array of tokens to be replaced, keyed by the literal text of the token
  136. * as it appeared in the source text.
  137. * @param $data
  138. * (optional) An array of keyed objects. For simple replacement scenarios
  139. * 'node', 'user', and others are common keys, with an accompanying node or
  140. * user object being the value. Some token types, like 'site', do not require
  141. * any explicit information from $data and can be replaced even if it is
  142. * empty.
  143. * @param $options
  144. * (optional) A keyed array of settings and flags to control the token
  145. * replacement process. Supported options are:
  146. * - language: A language object to be used when generating locale-sensitive
  147. * tokens.
  148. * - callback: A callback function that will be used to post-process the
  149. * array of token replacements after they are generated. Can be used when
  150. * modules require special formatting of token text, for example URL
  151. * encoding or truncation to a specific length.
  152. * - sanitize: A boolean flag indicating that tokens should be sanitized for
  153. * display to a web browser. Developers who set this option to FALSE assume
  154. * responsibility for running filter_xss(), check_plain() or other
  155. * appropriate scrubbing functions before displaying data to users.
  156. *
  157. * @return
  158. * An associative array of replacement values, keyed by the original 'raw'
  159. * tokens that were found in the source text. For example:
  160. * $results['[node:title]'] = 'My new node';
  161. *
  162. * @see hook_tokens()
  163. * @see hook_tokens_alter()
  164. */
  165. function token_generate($type, array $tokens, array $data = array(), array $options = array()) {
  166. $options += array('sanitize' => TRUE);
  167. $replacements = module_invoke_all('tokens', $type, $tokens, $data, $options);
  168. // Allow other modules to alter the replacements.
  169. $context = array(
  170. 'type' => $type,
  171. 'tokens' => $tokens,
  172. 'data' => $data,
  173. 'options' => $options,
  174. );
  175. drupal_alter('tokens', $replacements, $context);
  176. return $replacements;
  177. }
  178. /**
  179. * Returns a list of tokens that begin with a specific prefix.
  180. *
  181. * Used to extract a group of 'chained' tokens (such as [node:author:name])
  182. * from the full list of tokens found in text. For example:
  183. * @code
  184. * $data = array(
  185. * 'author:name' => '[node:author:name]',
  186. * 'title' => '[node:title]',
  187. * 'created' => '[node:created]',
  188. * );
  189. * $results = token_find_with_prefix($data, 'author');
  190. * $results == array('name' => '[node:author:name]');
  191. * @endcode
  192. *
  193. * @param $tokens
  194. * A keyed array of tokens, and their original raw form in the source text.
  195. * @param $prefix
  196. * A textual string to be matched at the beginning of the token.
  197. * @param $delimiter
  198. * An optional string containing the character that separates the prefix from
  199. * the rest of the token. Defaults to ':'.
  200. *
  201. * @return
  202. * An associative array of discovered tokens, with the prefix and delimiter
  203. * stripped from the key.
  204. */
  205. function token_find_with_prefix(array $tokens, $prefix, $delimiter = ':') {
  206. $results = array();
  207. foreach ($tokens as $token => $raw) {
  208. $parts = explode($delimiter, $token, 2);
  209. if (count($parts) == 2 && $parts[0] == $prefix) {
  210. $results[$parts[1]] = $raw;
  211. }
  212. }
  213. return $results;
  214. }
  215. /**
  216. * Returns metadata describing supported tokens.
  217. *
  218. * The metadata array contains token type, name, and description data as well
  219. * as an optional pointer indicating that the token chains to another set of
  220. * tokens.
  221. *
  222. * For example:
  223. * @code
  224. * $data['types']['node'] = array(
  225. * 'name' => t('Nodes'),
  226. * 'description' => t('Tokens related to node objects.'),
  227. * );
  228. * $data['tokens']['node']['title'] = array(
  229. * 'name' => t('Title'),
  230. * 'description' => t('The title of the current node.'),
  231. * );
  232. * $data['tokens']['node']['author'] = array(
  233. * 'name' => t('Author'),
  234. * 'description' => t('The author of the current node.'),
  235. * 'type' => 'user',
  236. * );
  237. * @endcode
  238. *
  239. * @return
  240. * An associative array of token information, grouped by token type.
  241. */
  242. function token_info() {
  243. $data = &drupal_static(__FUNCTION__);
  244. if (!isset($data)) {
  245. $data = module_invoke_all('token_info');
  246. drupal_alter('token_info', $data);
  247. }
  248. return $data;
  249. }