token.tokens.inc 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460
  1. <?php
  2. /**
  3. * @file
  4. * Token callbacks for the token module.
  5. */
  6. /**
  7. * Implements hook_token_info_alter().
  8. */
  9. function token_token_info_alter(&$info) {
  10. // Force 'date' type tokens to require input and add a 'current-date' type.
  11. // @todo Remove when http://drupal.org/node/943028 is fixed.
  12. $info['types']['date']['needs-data'] = 'date';
  13. $info['types']['current-date'] = array(
  14. 'name' => t('Current date'),
  15. 'description' => t('Tokens related to the current date and time.'),
  16. 'type' => 'date',
  17. );
  18. // Add a 'dynamic' key to any tokens that have chained but dynamic tokens.
  19. $info['tokens']['date']['custom']['dynamic'] = TRUE;
  20. // The [file:size] may not always return in kilobytes.
  21. // @todo Remove when http://drupal.org/node/1193044 is fixed.
  22. $info['tokens']['file']['size']['description'] = t('The size of the file.');
  23. // Remove deprecated tokens from being listed.
  24. unset($info['tokens']['node']['tnid']);
  25. unset($info['tokens']['node']['type']);
  26. unset($info['tokens']['node']['type-name']);
  27. // Support 'url' type tokens for core tokens.
  28. if (isset($info['tokens']['comment']['url']) && module_exists('comment')) {
  29. $info['tokens']['comment']['url']['type'] = 'url';
  30. }
  31. $info['tokens']['node']['url']['type'] = 'url';
  32. if (isset($info['tokens']['term']['url']) && module_exists('taxonomy')) {
  33. $info['tokens']['term']['url']['type'] = 'url';
  34. }
  35. $info['tokens']['user']['url']['type'] = 'url';
  36. // Add [token:url] tokens for any URI-able entities.
  37. $entities = entity_get_info();
  38. foreach ($entities as $entity => $entity_info) {
  39. if (!isset($entity_info['token type'])) {
  40. continue;
  41. }
  42. $token_type = $entity_info['token type'];
  43. if (!isset($info['types'][$token_type]) || !isset($info['tokens'][$token_type])) {
  44. continue;
  45. }
  46. // Add [entity:url] tokens if they do not already exist.
  47. // @todo Support entity:label
  48. if (!isset($info['tokens'][$token_type]['url']) && !empty($entity_info['uri callback'])) {
  49. $info['tokens'][$token_type]['url'] = array(
  50. 'name' => t('URL'),
  51. 'description' => t('The URL of the @entity.', array('@entity' => drupal_strtolower($entity_info['label']))),
  52. 'module' => 'token',
  53. 'type' => 'url',
  54. );
  55. }
  56. // Add [entity:original] tokens if they do not already exist.
  57. if (!isset($info['tokens'][$token_type]['original'])) {
  58. $info['tokens'][$token_type]['original'] = array(
  59. 'name' => t('Original @entity', array('@entity' => drupal_strtolower($entity_info['label']))),
  60. 'description' => t('The original @entity data if the @entity is being updated or saved.', array('@entity' => drupal_strtolower($entity_info['label']))),
  61. 'module' => 'token',
  62. 'type' => $token_type,
  63. );
  64. }
  65. }
  66. // Add support for custom date formats.
  67. // @todo Remove when http://drupal.org/node/1173706 is fixed.
  68. $date_format_types = system_get_date_types();
  69. foreach ($date_format_types as $date_format_type => $date_format_type_info) {
  70. if (!isset($info['tokens']['date'][$date_format_type])) {
  71. $info['tokens']['date'][$date_format_type] = array(
  72. 'name' => check_plain($date_format_type_info['title']),
  73. 'description' => t("A date in '@type' format. (%date)", array('@type' => $date_format_type, '%date' => format_date(REQUEST_TIME, $date_format_type))),
  74. 'module' => 'token',
  75. );
  76. }
  77. }
  78. }
  79. /**
  80. * Implements hook_token_info().
  81. */
  82. function token_token_info() {
  83. // Node tokens.
  84. $info['tokens']['node']['source'] = array(
  85. 'name' => t('Translation source node'),
  86. 'description' => t("The source node for this current node's translation set."),
  87. 'type' => 'node',
  88. );
  89. $info['tokens']['node']['log'] = array(
  90. 'name' => t('Revision log message'),
  91. 'description' => t('The explanation of the most recent changes made to the node.'),
  92. );
  93. $info['tokens']['node']['content-type'] = array(
  94. 'name' => t('Content type'),
  95. 'description' => t('The content type of the node.'),
  96. 'type' => 'content-type',
  97. );
  98. // Content type tokens.
  99. $info['types']['content-type'] = array(
  100. 'name' => t('Content types'),
  101. 'description' => t('Tokens related to content types.'),
  102. 'needs-data' => 'node_type',
  103. );
  104. $info['tokens']['content-type']['name'] = array(
  105. 'name' => t('Name'),
  106. 'description' => t('The name of the content type.'),
  107. );
  108. $info['tokens']['content-type']['machine-name'] = array(
  109. 'name' => t('Machine-readable name'),
  110. 'description' => t('The unique machine-readable name of the content type.'),
  111. );
  112. $info['tokens']['content-type']['description'] = array(
  113. 'name' => t('Description'),
  114. 'description' => t('The optional description of the content type.'),
  115. );
  116. $info['tokens']['content-type']['node-count'] = array(
  117. 'name' => t('Node count'),
  118. 'description' => t('The number of nodes belonging to the content type.'),
  119. );
  120. $info['tokens']['content-type']['edit-url'] = array(
  121. 'name' => t('Edit URL'),
  122. 'description' => t("The URL of the content type's edit page."),
  123. // 'type' => 'url',
  124. );
  125. // Taxonomy term and vocabulary tokens.
  126. if (module_exists('taxonomy')) {
  127. $info['tokens']['term']['edit-url'] = array(
  128. 'name' => t('Edit URL'),
  129. 'description' => t("The URL of the taxonomy term's edit page."),
  130. // 'type' => 'url',
  131. );
  132. $info['tokens']['term']['parents'] = array(
  133. 'name' => t('Parents'),
  134. 'description' => t("An array of all the term's parents, starting with the root."),
  135. 'type' => 'array',
  136. );
  137. $info['tokens']['term']['root'] = array(
  138. 'name' => t('Root term'),
  139. 'description' => t("The root term of the taxonomy term."),
  140. 'type' => 'term',
  141. );
  142. $info['tokens']['vocabulary']['machine-name'] = array(
  143. 'name' => t('Machine-readable name'),
  144. 'description' => t('The unique machine-readable name of the vocabulary.'),
  145. );
  146. $info['tokens']['vocabulary']['edit-url'] = array(
  147. 'name' => t('Edit URL'),
  148. 'description' => t("The URL of the vocabulary's edit page."),
  149. // 'type' => 'url',
  150. );
  151. }
  152. // File tokens.
  153. $info['tokens']['file']['basename'] = array(
  154. 'name' => t('Base name'),
  155. 'description' => t('The base name of the file.'),
  156. );
  157. $info['tokens']['file']['extension'] = array(
  158. 'name' => t('Extension'),
  159. 'description' => t('The extension of the file.'),
  160. );
  161. $info['tokens']['file']['size-raw'] = array(
  162. 'name' => t('File byte size'),
  163. 'description' => t('The size of the file, in bytes.'),
  164. );
  165. // User tokens.
  166. // Add information on the restricted user tokens.
  167. $info['tokens']['user']['cancel-url'] = array(
  168. 'name' => t('Account cancellation URL'),
  169. 'description' => t('The URL of the confirm delete page for the user account.'),
  170. 'restricted' => TRUE,
  171. // 'type' => 'url',
  172. );
  173. $info['tokens']['user']['one-time-login-url'] = array(
  174. 'name' => t('One-time login URL'),
  175. 'description' => t('The URL of the one-time login page for the user account.'),
  176. 'restricted' => TRUE,
  177. // 'type' => 'url',
  178. );
  179. if (variable_get('user_pictures', 0)) {
  180. $info['tokens']['user']['picture'] = array(
  181. 'name' => t('Picture'),
  182. 'description' => t('The picture of the user.'),
  183. 'type' => 'file',
  184. );
  185. }
  186. $info['tokens']['user']['roles'] = array(
  187. 'name' => t('Roles'),
  188. 'description' => t('The user roles associated with the user account.'),
  189. 'type' => 'array',
  190. );
  191. // Current user tokens.
  192. $info['tokens']['current-user']['ip-address'] = array(
  193. 'name' => t('IP address'),
  194. 'description' => 'The IP address of the current user.',
  195. );
  196. // Menu link tokens (work regardless if menu module is enabled or not).
  197. $info['types']['menu-link'] = array(
  198. 'name' => t('Menu links'),
  199. 'description' => t('Tokens related to menu links.'),
  200. 'needs-data' => 'menu-link',
  201. );
  202. $info['tokens']['menu-link']['mlid'] = array(
  203. 'name' => t('Link ID'),
  204. 'description' => t('The unique ID of the menu link.'),
  205. );
  206. $info['tokens']['menu-link']['title'] = array(
  207. 'name' => t('Title'),
  208. 'description' => t('The title of the menu link.'),
  209. );
  210. $info['tokens']['menu-link']['url'] = array(
  211. 'name' => t('URL'),
  212. 'description' => t('The URL of the menu link.'),
  213. 'type' => 'url',
  214. );
  215. $info['tokens']['menu-link']['parent'] = array(
  216. 'name' => t('Parent'),
  217. 'description' => t("The menu link's parent."),
  218. 'type' => 'menu-link',
  219. );
  220. $info['tokens']['menu-link']['parents'] = array(
  221. 'name' => t('Parents'),
  222. 'description' => t("An array of all the menu link's parents, starting with the root."),
  223. 'type' => 'array',
  224. );
  225. $info['tokens']['menu-link']['root'] = array(
  226. 'name' => t('Root'),
  227. 'description' => t("The menu link's root."),
  228. 'type' => 'menu-link',
  229. );
  230. // Current page tokens.
  231. $info['types']['current-page'] = array(
  232. 'name' => t('Current page'),
  233. 'description' => t('Tokens related to the current page request.'),
  234. );
  235. $info['tokens']['current-page']['title'] = array(
  236. 'name' => t('Title'),
  237. 'description' => t('The title of the current page.'),
  238. );
  239. $info['tokens']['current-page']['url'] = array(
  240. 'name' => t('URL'),
  241. 'description' => t('The URL of the current page.'),
  242. 'type' => 'url',
  243. );
  244. $info['tokens']['current-page']['page-number'] = array(
  245. 'name' => t('Page number'),
  246. 'description' => t('The page number of the current page when viewing paged lists.'),
  247. );
  248. $info['tokens']['current-page']['query'] = array(
  249. 'name' => t('Query string value'),
  250. 'description' => t('The value of a specific query string field of the current page.'),
  251. 'dynamic' => TRUE,
  252. );
  253. // URL tokens.
  254. $info['types']['url'] = array(
  255. 'name' => t('URL'),
  256. 'description' => t('Tokens related to URLs.'),
  257. 'needs-data' => 'path',
  258. );
  259. $info['tokens']['url']['path'] = array(
  260. 'name' => t('Path'),
  261. 'description' => t('The path component of the URL.'),
  262. );
  263. $info['tokens']['url']['relative'] = array(
  264. 'name' => t('Relative URL'),
  265. 'description' => t('The relative URL.'),
  266. );
  267. $info['tokens']['url']['absolute'] = array(
  268. 'name' => t('Absolute URL'),
  269. 'description' => t('The absolute URL.'),
  270. );
  271. $info['tokens']['url']['brief'] = array(
  272. 'name' => t('Brief URL'),
  273. 'description' => t('The URL without the protocol and trailing backslash.'),
  274. );
  275. $info['tokens']['url']['unaliased'] = array(
  276. 'name' => t('Unaliased URL'),
  277. 'description' => t('The unaliased URL.'),
  278. 'type' => 'url',
  279. );
  280. $info['tokens']['url']['args'] = array(
  281. 'name' => t('Arguments'),
  282. 'description' => t("The specific argument of the current page (e.g. 'arg:1' on the page 'node/1' returns '1')."),
  283. 'type' => 'array',
  284. );
  285. // Array tokens.
  286. $info['types']['array'] = array(
  287. 'name' => t('Array'),
  288. 'description' => t('Tokens related to arrays of strings.'),
  289. 'needs-data' => 'array',
  290. );
  291. $info['tokens']['array']['first'] = array(
  292. 'name' => t('First'),
  293. 'description' => t('The first element of the array.'),
  294. );
  295. $info['tokens']['array']['last'] = array(
  296. 'name' => t('Last'),
  297. 'description' => t('The last element of the array.'),
  298. );
  299. $info['tokens']['array']['count'] = array(
  300. 'name' => t('Count'),
  301. 'description' => t('The number of elements in the array.'),
  302. );
  303. $info['tokens']['array']['reversed'] = array(
  304. 'name' => t('Reversed'),
  305. 'description' => t('The array reversed.'),
  306. 'type' => 'array',
  307. );
  308. $info['tokens']['array']['keys'] = array(
  309. 'name' => t('Keys'),
  310. 'description' => t('The array of keys of the array.'),
  311. 'type' => 'array',
  312. );
  313. $info['tokens']['array']['join'] = array(
  314. 'name' => t('Imploded'),
  315. 'description' => t('The values of the array joined together with a custom string in-between each value.'),
  316. 'dynamic' => TRUE,
  317. );
  318. $info['tokens']['array']['value'] = array(
  319. 'name' => t('Value'),
  320. 'description' => t('The specific value of the array.'),
  321. 'dynamic' => TRUE,
  322. );
  323. // Random tokens.
  324. $info['types']['random'] = array(
  325. 'name' => t('Random'),
  326. 'description' => ('Tokens related to random data.'),
  327. );
  328. $info['tokens']['random']['number'] = array(
  329. 'name' => t('Number'),
  330. 'description' => t('A random number from 0 to @max.', array('@max' => mt_getrandmax())),
  331. );
  332. $info['tokens']['random']['hash'] = array(
  333. 'name' => t('Hash'),
  334. 'description' => t('A random hash. The possible hashing algorithms are: @hash-algos.', array('@hash-algos' => implode(', ', hash_algos()))),
  335. 'dynamic' => TRUE,
  336. );
  337. return $info;
  338. }
  339. /**
  340. * Implements hook_tokens().
  341. */
  342. function token_tokens($type, $tokens, array $data = array(), array $options = array()) {
  343. $replacements = array();
  344. $url_options = array('absolute' => TRUE);
  345. if (isset($options['language'])) {
  346. $url_options['language'] = $options['language'];
  347. $language_code = $options['language']->language;
  348. }
  349. else {
  350. $language_code = NULL;
  351. }
  352. $sanitize = !empty($options['sanitize']);
  353. // Date tokens.
  354. if ($type == 'date') {
  355. $date = !empty($data['date']) ? $data['date'] : REQUEST_TIME;
  356. // @todo Remove when http://drupal.org/node/1173706 is fixed.
  357. $date_format_types = system_get_date_types();
  358. foreach ($tokens as $name => $original) {
  359. if (isset($date_format_types[$name]) && _token_module('date', $name) == 'token') {
  360. $replacements[$original] = format_date($date, $name, '', NULL, $language_code);
  361. }
  362. }
  363. }
  364. // Current date tokens.
  365. // @todo Remove when http://drupal.org/node/943028 is fixed.
  366. if ($type == 'current-date') {
  367. $replacements += token_generate('date', $tokens, array('date' => REQUEST_TIME), $options);
  368. }
  369. // Comment tokens.
  370. if ($type == 'comment' && !empty($data['comment'])) {
  371. $comment = $data['comment'];
  372. // Chained token relationships.
  373. if (($url_tokens = token_find_with_prefix($tokens, 'url'))) {
  374. $replacements += token_generate('url', $url_tokens, entity_uri('comment', $comment), $options);
  375. }
  376. }
  377. // Node tokens.
  378. if ($type == 'node' && !empty($data['node'])) {
  379. $node = $data['node'];
  380. foreach ($tokens as $name => $original) {
  381. switch ($name) {
  382. case 'source':
  383. if (!empty($node->tnid) && $source_node = node_load($node->tnid)) {
  384. $title = $source_node->title;
  385. $replacements[$original] = $sanitize ? filter_xss($title) : $title;
  386. }
  387. break;
  388. case 'log':
  389. $replacements[$original] = $sanitize ? filter_xss($node->log) : $node->log;
  390. break;
  391. case 'content-type':
  392. $type_name = node_type_get_name($node);
  393. $replacements[$original] = $sanitize ? check_plain($type_name) : $type_name;
  394. break;
  395. }
  396. }
  397. // Chained token relationships.
  398. if (!empty($node->tnid) && ($source_tokens = token_find_with_prefix($tokens, 'source')) && $source_node = node_load($node->tnid)) {
  399. $replacements += token_generate('node', $source_tokens, array('node' => $source_node), $options);
  400. }
  401. if (($node_type_tokens = token_find_with_prefix($tokens, 'content-type')) && $node_type = node_type_load($node->type)) {
  402. $replacements += token_generate('content-type', $node_type_tokens, array('node_type' => $node_type), $options);
  403. }
  404. if (($url_tokens = token_find_with_prefix($tokens, 'url'))) {
  405. $replacements += token_generate('url', $url_tokens, entity_uri('node', $node), $options);
  406. }
  407. }
  408. // Content type tokens.
  409. if ($type == 'content-type' && !empty($data['node_type'])) {
  410. $node_type = $data['node_type'];
  411. foreach ($tokens as $name => $original) {
  412. switch ($name) {
  413. case 'name':
  414. $replacements[$original] = $sanitize ? check_plain($node_type->name) : $node_type->name;
  415. break;
  416. case 'machine-name':
  417. // This is a machine name so does not ever need to be sanitized.
  418. $replacements[$original] = $node_type->type;
  419. break;
  420. case 'description':
  421. $replacements[$original] = $sanitize ? filter_xss($node_type->description) : $node_type->description;
  422. break;
  423. case 'node-count':
  424. $query = db_select('node');
  425. $query->condition('type', $node_type->type);
  426. $query->addTag('node_type_node_count');
  427. $count = $query->countQuery()->execute()->fetchField();
  428. $replacements[$original] = (int) $count;
  429. break;
  430. case 'edit-url':
  431. $replacements[$original] = url("admin/structure/types/manage/{$node_type->type}", $url_options);
  432. break;
  433. }
  434. }
  435. }
  436. // Taxonomy term tokens.
  437. if ($type == 'term' && !empty($data['term'])) {
  438. $term = $data['term'];
  439. foreach ($tokens as $name => $original) {
  440. switch ($name) {
  441. case 'edit-url':
  442. $replacements[$original] = url("taxonomy/term/{$term->tid}/edit", $url_options);
  443. break;
  444. case 'parents':
  445. if ($parents = token_taxonomy_term_load_all_parents($term->tid)) {
  446. $replacements[$original] = token_render_array($parents, $options);
  447. }
  448. break;
  449. case 'root':
  450. $parents = taxonomy_get_parents_all($term->tid);
  451. $root_term = end($parents);
  452. if ($root_term->tid != $term->tid) {
  453. $replacements[$original] = $sanitize ? check_plain($root_term->name) : $root_term->name;
  454. }
  455. break;
  456. }
  457. }
  458. // Chained token relationships.
  459. if (($url_tokens = token_find_with_prefix($tokens, 'url'))) {
  460. $replacements += token_generate('url', $url_tokens, entity_uri('taxonomy_term', $term), $options);
  461. }
  462. // [term:parents:*] chained tokens.
  463. if ($parents_tokens = token_find_with_prefix($tokens, 'parents')) {
  464. if ($parents = token_taxonomy_term_load_all_parents($term->tid)) {
  465. $replacements += token_generate('array', $parents_tokens, array('array' => $parents), $options);
  466. }
  467. }
  468. if ($root_tokens = token_find_with_prefix($tokens, 'root')) {
  469. $parents = taxonomy_get_parents_all($term->tid);
  470. $root_term = end($parents);
  471. if ($root_term->tid != $term->tid) {
  472. $replacements += token_generate('term', $root_tokens, array('term' => $root_term), $options);
  473. }
  474. }
  475. }
  476. // Vocabulary tokens.
  477. if ($type == 'vocabulary' && !empty($data['vocabulary'])) {
  478. $vocabulary = $data['vocabulary'];
  479. foreach ($tokens as $name => $original) {
  480. switch ($name) {
  481. case 'machine-name':
  482. // This is a machine name so does not ever need to be sanitized.
  483. $replacements[$original] = $vocabulary->machine_name;
  484. break;
  485. case 'edit-url':
  486. $replacements[$original] = url("admin/structure/taxonomy/{$vocabulary->machine_name}/edit", $url_options);
  487. break;
  488. }
  489. }
  490. }
  491. // File tokens.
  492. if ($type == 'file' && !empty($data['file'])) {
  493. $file = $data['file'];
  494. foreach ($tokens as $name => $original) {
  495. switch ($name) {
  496. case 'basename':
  497. $basename = pathinfo($file->uri, PATHINFO_BASENAME);
  498. $replacements[$original] = $sanitize ? check_plain($basename) : $basename;
  499. break;
  500. case 'extension':
  501. $extension = pathinfo($file->uri, PATHINFO_EXTENSION);
  502. $replacements[$original] = $sanitize ? check_plain($extension) : $extension;
  503. break;
  504. case 'size-raw':
  505. $replacements[$original] = (int) $file->filesize;
  506. break;
  507. }
  508. }
  509. }
  510. // User tokens.
  511. if ($type == 'user' && !empty($data['user'])) {
  512. $account = $data['user'];
  513. foreach ($tokens as $name => $original) {
  514. switch ($name) {
  515. case 'picture':
  516. if (variable_get('user_pictures', 0)) {
  517. $replacements[$original] = theme('user_picture', array('account' => $account));
  518. }
  519. break;
  520. case 'roles':
  521. // The roles array may be set from checkbox values so ensure it always
  522. // has 'proper' data with the role names.
  523. $roles = array_intersect_key(user_roles(), $account->roles);
  524. $replacements[$original] = token_render_array($roles, $options);
  525. break;
  526. }
  527. }
  528. // Chained token relationships.
  529. if (variable_get('user_pictures', 0) && !empty($account->picture) && ($picture_tokens = token_find_with_prefix($tokens, 'picture'))) {
  530. // @todo Remove when core bug http://drupal.org/node/978028 is fixed.
  531. $account->picture->description = '';
  532. $replacements += token_generate('file', $picture_tokens, array('file' => $account->picture), $options);
  533. }
  534. if ($url_tokens = token_find_with_prefix($tokens, 'url')) {
  535. $replacements += token_generate('url', $url_tokens, entity_uri('user', $account), $options);
  536. }
  537. if ($role_tokens = token_find_with_prefix($tokens, 'roles')) {
  538. // The roles array may be set from checkbox values so ensure it always
  539. // has 'proper' data with the role names.
  540. $roles = array_intersect_key(user_roles(), $account->roles);
  541. $replacements += token_generate('array', $role_tokens, array('array' => $roles), $options);
  542. }
  543. }
  544. // Current user tokens.
  545. if ($type == 'current-user') {
  546. foreach ($tokens as $name => $original) {
  547. switch ($name) {
  548. case 'ip-address':
  549. $ip = ip_address();
  550. $replacements[$original] = $sanitize ? check_plain($ip) : $ip;
  551. break;
  552. }
  553. }
  554. }
  555. // Menu link tokens.
  556. if ($type == 'menu-link' && !empty($data['menu-link'])) {
  557. $link = (array) $data['menu-link'];
  558. if (!isset($link['title'])) {
  559. // Re-load the link if it was not loaded via token_menu_link_load().
  560. $link = token_menu_link_load($link['mlid']);
  561. }
  562. foreach ($tokens as $name => $original) {
  563. switch ($name) {
  564. case 'mlid':
  565. $replacements[$original] = $link['mlid'];
  566. break;
  567. case 'title':
  568. $replacements[$original] = $sanitize ? check_plain($link['title']) : $link['title'];
  569. break;
  570. case 'url':
  571. $replacements[$original] = url($link['href'], $url_options);
  572. break;
  573. case 'parent':
  574. if (!empty($link['plid']) && $parent = token_menu_link_load($link['plid'])) {
  575. $replacements[$original] = $sanitize ? check_plain($parent['title']) : $parent['title'];
  576. }
  577. break;
  578. case 'parents':
  579. if ($parents = token_menu_link_load_all_parents($link['mlid'])) {
  580. $replacements[$original] = token_render_array($parents, $options);
  581. }
  582. break;
  583. case 'root';
  584. if (!empty($link['p1']) && $link['p1'] != $link['mlid'] && $root = token_menu_link_load($link['p1'])) {
  585. $replacements[$original] = $sanitize ? check_plain($root['title']) : $root['title'];
  586. }
  587. break;
  588. }
  589. }
  590. // Chained token relationships.
  591. if (!empty($link['plid']) && ($source_tokens = token_find_with_prefix($tokens, 'parent')) && $parent = token_menu_link_load($link['plid'])) {
  592. $replacements += token_generate('menu-link', $source_tokens, array('menu-link' => $parent), $options);
  593. }
  594. // [menu-link:parents:*] chained tokens.
  595. if ($parents_tokens = token_find_with_prefix($tokens, 'parents')) {
  596. if ($parents = token_menu_link_load_all_parents($link['mlid'])) {
  597. $replacements += token_generate('array', $parents_tokens, array('array' => $parents), $options);
  598. }
  599. }
  600. if (!empty($link['p1']) && $link['p1'] != $link['mlid'] && ($root_tokens = token_find_with_prefix($tokens, 'root')) && $root = token_menu_link_load($link['p1'])) {
  601. $replacements += token_generate('menu-link', $root_tokens, array('menu-link' => $root), $options);
  602. }
  603. if ($url_tokens = token_find_with_prefix($tokens, 'url')) {
  604. $replacements += token_generate('url', $url_tokens, array('path' => $link['href']), $options);
  605. }
  606. }
  607. // Current page tokens.
  608. if ($type == 'current-page') {
  609. $current_path = current_path();
  610. foreach ($tokens as $name => $original) {
  611. switch ($name) {
  612. case 'title':
  613. $title = drupal_get_title();
  614. $replacements[$original] = $sanitize ? $title : decode_entities($title);
  615. break;
  616. case 'url':
  617. $replacements[$original] = url($current_path, $url_options);
  618. break;
  619. case 'page-number':
  620. if ($page = filter_input(INPUT_GET, 'page')) {
  621. // @see PagerDefault::execute()
  622. $pager_page_array = explode(',', $page);
  623. $page = $pager_page_array[0];
  624. }
  625. $replacements[$original] = (int) $page + 1;
  626. break;
  627. }
  628. }
  629. // @deprecated
  630. // [current-page:arg] dynamic tokens.
  631. if ($arg_tokens = token_find_with_prefix($tokens, 'arg')) {
  632. foreach ($arg_tokens as $name => $original) {
  633. if (is_numeric($name) && ($arg = arg($name)) && isset($arg)) {
  634. $replacements[$original] = $sanitize ? check_plain($arg) : $arg;
  635. }
  636. }
  637. }
  638. // [current-page:query] dynamic tokens.
  639. if ($query_tokens = token_find_with_prefix($tokens, 'query')) {
  640. foreach ($query_tokens as $name => $original) {
  641. // @todo Should this use filter_input()?
  642. if (isset($_GET[$name])) {
  643. $replacements[$original] = $sanitize ? check_plain($_GET[$name]) : $_GET[$name];
  644. }
  645. }
  646. }
  647. // Chained token relationships.
  648. if ($url_tokens = token_find_with_prefix($tokens, 'url')) {
  649. $replacements += token_generate('url', $url_tokens, array('path' => $current_path), $options);
  650. }
  651. }
  652. // URL tokens.
  653. if ($type == 'url' && !empty($data['path'])) {
  654. $path = $data['path'];
  655. if (isset($data['options'])) {
  656. // Merge in the URL options if available.
  657. $url_options = $data['options'] + $url_options;
  658. }
  659. foreach ($tokens as $name => $original) {
  660. switch ($name) {
  661. case 'path':
  662. $value = empty($url_options['alias']) ? drupal_get_path_alias($path, $language_code) : $path;
  663. $replacements[$original] = $sanitize ? check_plain($value) : $value;
  664. break;
  665. case 'alias':
  666. // @deprecated
  667. $alias = drupal_get_path_alias($path, $language_code);
  668. $replacements[$original] = $sanitize ? check_plain($alias) : $alias;
  669. break;
  670. case 'absolute':
  671. $replacements[$original] = url($path, $url_options);
  672. break;
  673. case 'relative':
  674. $replacements[$original] = url($path, array('absolute' => FALSE) + $url_options);
  675. break;
  676. case 'brief':
  677. $replacements[$original] = preg_replace(array('!^https?://!', '!/$!'), '', url($path, $url_options));
  678. break;
  679. case 'unaliased':
  680. $replacements[$original] = url($path, array('alias' => TRUE) + $url_options);
  681. break;
  682. case 'args':
  683. $value = empty($url_options['alias']) ? drupal_get_path_alias($path, $language_code) : $path;
  684. $replacements[$original] = token_render_array(arg(NULL, $value), $options);
  685. break;
  686. }
  687. }
  688. // [url:arg:*] chained tokens.
  689. if ($arg_tokens = token_find_with_prefix($tokens, 'args')) {
  690. $value = empty($url_options['alias']) ? drupal_get_path_alias($path, $language_code) : $path;
  691. $replacements += token_generate('array', $arg_tokens, array('array' => arg(NULL, $value)), $options);
  692. }
  693. // [url:unaliased:*] chained tokens.
  694. if ($unaliased_tokens = token_find_with_prefix($tokens, 'unaliased')) {
  695. $unaliased_token_data['path'] = $path;
  696. $unaliased_token_data['options'] = isset($data['options']) ? $data['options'] : array();
  697. $unaliased_token_data['options']['alias'] = TRUE;
  698. $replacements += token_generate('url', $unaliased_tokens, $unaliased_token_data, $options);
  699. }
  700. }
  701. // Entity tokens.
  702. if (!empty($data[$type]) && $entity_type = token_get_entity_mapping('token', $type)) {
  703. $entity = $data[$type];
  704. // Sometimes taxonomy terms are not properly loaded.
  705. // @see http://drupal.org/node/870528
  706. if ($entity_type == 'taxonomy_term' && !isset($entity->vocabulary_machine_name)) {
  707. $entity->vocabulary_machine_name = db_query("SELECT machine_name FROM {taxonomy_vocabulary} WHERE vid = :vid", array(':vid' => $entity->vid))->fetchField();
  708. }
  709. foreach ($tokens as $name => $original) {
  710. switch ($name) {
  711. case 'url':
  712. if (_token_module($type, 'url') == 'token' && $uri = entity_uri($entity_type, $entity)) {
  713. $replacements[$original] = url($uri['path'], $uri['options']);
  714. }
  715. break;
  716. case 'original':
  717. if (_token_module($type, 'original') == 'token' && !empty($entity->original)) {
  718. $label = entity_label($entity_type, $entity->original);
  719. $replacements[$original] = $sanitize ? check_plain($label) : $label;
  720. }
  721. break;
  722. }
  723. }
  724. // [entity:url:*] chained tokens.
  725. if (($url_tokens = token_find_with_prefix($tokens, 'url')) && _token_module($type, 'url') == 'token') {
  726. $replacements += token_generate('url', $url_tokens, entity_uri($entity_type, $entity), $options);
  727. }
  728. // [entity:original:*] chained tokens.
  729. if (($original_tokens = token_find_with_prefix($tokens, 'original')) && _token_module($type, 'original') == 'token' && !empty($entity->original)) {
  730. $replacements += token_generate($type, $original_tokens, array($type => $entity->original), $options);
  731. }
  732. // Pass through to an generic 'entity' token type generation.
  733. $entity_data = array(
  734. 'entity_type' => $entity_type,
  735. 'entity' => $entity,
  736. 'token_type' => $type,
  737. );
  738. // @todo Investigate passing through more data like everything from entity_extract_ids().
  739. $replacements += token_generate('entity', $tokens, $entity_data, $options);
  740. }
  741. // Array tokens.
  742. if ($type == 'array' && !empty($data['array']) && is_array($data['array'])) {
  743. $array = $data['array'];
  744. $sort = isset($options['array sort']) ? $options['array sort'] : TRUE;
  745. $keys = element_children($array, $sort);
  746. foreach ($tokens as $name => $original) {
  747. switch ($name) {
  748. case 'first':
  749. $value = $array[$keys[0]];
  750. $value = is_array($value) ? render($value) : (string) $value;
  751. $replacements[$original] = $sanitize ? check_plain($value) : $value;
  752. break;
  753. case 'last':
  754. $value = $array[$keys[count($keys) - 1]];
  755. $value = is_array($value) ? render($value) : (string) $value;
  756. $replacements[$original] = $sanitize ? check_plain($value) : $value;
  757. break;
  758. case 'count':
  759. $replacements[$original] = count($keys);
  760. break;
  761. case 'keys':
  762. $replacements[$original] = token_render_array($keys, $options);
  763. break;
  764. case 'reversed':
  765. $reversed = array_reverse($array, TRUE);
  766. $replacements[$original] = token_render_array($reversed, $options);
  767. break;
  768. case 'join':
  769. $replacements[$original] = token_render_array($array, array('join' => '') + $options);
  770. break;
  771. }
  772. }
  773. // [array:value:*] dynamic tokens.
  774. if ($value_tokens = token_find_with_prefix($tokens, 'value')) {
  775. foreach ($value_tokens as $key => $original) {
  776. if ($key[0] !== '#' && isset($array[$key])) {
  777. $replacements[$original] = token_render_array_value($array[$key], $options);
  778. }
  779. }
  780. }
  781. // [array:join:*] dynamic tokens.
  782. if ($join_tokens = token_find_with_prefix($tokens, 'join')) {
  783. foreach ($join_tokens as $join => $original) {
  784. $replacements[$original] = token_render_array($array, array('join' => $join) + $options);
  785. }
  786. }
  787. // [array:keys:*] chained tokens.
  788. if ($key_tokens = token_find_with_prefix($tokens, 'keys')) {
  789. $replacements += token_generate('array', $key_tokens, array('array' => $keys), $options);
  790. }
  791. // [array:reversed:*] chained tokens.
  792. if ($reversed_tokens = token_find_with_prefix($tokens, 'reversed')) {
  793. $replacements += token_generate('array', $reversed_tokens, array('array' => array_reverse($array, TRUE)), array('array sort' => FALSE) + $options);
  794. }
  795. // @todo Handle if the array values are not strings and could be chained.
  796. }
  797. // Random tokens.
  798. if ($type == 'random') {
  799. foreach ($tokens as $name => $original) {
  800. switch ($name) {
  801. case 'number':
  802. $replacements[$original] = mt_rand();
  803. break;
  804. }
  805. }
  806. // [custom:hash:*] dynamic token.
  807. if ($hash_tokens = token_find_with_prefix($tokens, 'hash')) {
  808. $algos = hash_algos();
  809. foreach ($hash_tokens as $name => $original) {
  810. if (in_array($name, $algos)) {
  811. $replacements[$original] = hash($name, drupal_random_bytes(55));
  812. }
  813. }
  814. }
  815. }
  816. // If $type is a token type, $data[$type] is empty but $data[$entity_type] is
  817. // not, re-run token replacements.
  818. if (empty($data[$type]) && ($entity_type = token_get_entity_mapping('token', $type)) && $entity_type != $type && !empty($data[$entity_type]) && empty($options['recursive'])) {
  819. $data[$type] = $data[$entity_type];
  820. $options['recursive'] = TRUE;
  821. $replacements += module_invoke_all('tokens', $type, $tokens, $data, $options);
  822. }
  823. // If the token type specifics a 'needs-data' value, and the value is not
  824. // present in $data, then throw an error.
  825. if (!empty($GLOBALS['drupal_test_info']['test_run_id'])) {
  826. // Only check when tests are running.
  827. $type_info = token_get_info($type);
  828. if (!empty($type_info['needs-data']) && !isset($data[$type_info['needs-data']])) {
  829. trigger_error(t('Attempting to perform token replacement for token type %type without required data', array('%type' => $type)), E_USER_WARNING);
  830. }
  831. }
  832. return $replacements;
  833. }
  834. /**
  835. * Implements hook_tokens_alter().
  836. *
  837. * Fix existing core tokens that do not work correctly.
  838. */
  839. function token_tokens_alter(array &$replacements, array $context) {
  840. $options = $context['options'];
  841. $sanitize = !empty($options['sanitize']);
  842. $langcode = !empty($options['language']->language) ? $options['language']->language : NULL;
  843. // Comment token fixes.
  844. if ($context['type'] == 'comment' && !empty($context['data']['comment'])) {
  845. $comment = $context['data']['comment'];
  846. foreach ($context['tokens'] as $name => $original) {
  847. switch ($name) {
  848. case 'name':
  849. case 'author':
  850. // @todo Remove when http://drupal.org/node/920056 is fixed.
  851. if (!empty($comment->uid)) {
  852. $account = user_load($comment->uid);
  853. }
  854. else {
  855. $account = drupal_anonymous_user();
  856. $account->name = $comment->name;
  857. }
  858. $name = format_username($account);
  859. $replacements[$original] = $sanitize ? check_plain($name) : $name;
  860. break;
  861. }
  862. }
  863. }
  864. // Node token fixes.
  865. if ($context['type'] == 'node' && !empty($context['data']['node'])) {
  866. $node = $context['data']['node'];
  867. foreach ($context['tokens'] as $name => $original) {
  868. switch ($name) {
  869. case 'author':
  870. // http://drupal.org/node/1185842 was fixed in core release 7.9.
  871. if (version_compare(VERSION, '7.9', '<')) {
  872. $account = user_load($node->uid);
  873. $name = format_username($account);
  874. $replacements[$original] = $sanitize ? check_plain($name) : $name;
  875. }
  876. break;
  877. }
  878. }
  879. }
  880. // File token fixes.
  881. if ($context['type'] == 'file' && !empty($context['data']['file'])) {
  882. $file = $context['data']['file'];
  883. foreach ($context['tokens'] as $name => $original) {
  884. switch ($name) {
  885. case 'owner':
  886. // http://drupal.org/node/978028 was fixed in core release 7.7.
  887. if (version_compare(VERSION, '7.7', '<')) {
  888. $account = user_load($file->uid);
  889. $name = format_username($account);
  890. $replacements[$original] = $sanitize ? check_plain($name) : $name;
  891. }
  892. break;
  893. }
  894. }
  895. }
  896. }
  897. /**
  898. * Implements hook_token_info() on behalf of book.module.
  899. */
  900. function book_token_info() {
  901. $info['tokens']['node']['book'] = array(
  902. 'name' => t('Book'),
  903. 'description' => t('The book page associated with the node.'),
  904. 'type' => 'menu-link',
  905. );
  906. return $info;
  907. }
  908. /**
  909. * Implements hook_tokens() on behalf of book.module.
  910. */
  911. function book_tokens($type, $tokens, array $data = array(), array $options = array()) {
  912. $replacements = array();
  913. $sanitize = !empty($options['sanitize']);
  914. // Node tokens.
  915. if ($type == 'node' && !empty($data['node'])) {
  916. $node = $data['node'];
  917. if (!empty($node->book['mlid'])) {
  918. $link = token_book_link_load($node->book['mlid']);
  919. foreach ($tokens as $name => $original) {
  920. switch ($name) {
  921. case 'book':
  922. $replacements[$original] = $sanitize ? check_plain($link['title']) : $link['title'];
  923. break;
  924. }
  925. }
  926. // Chained token relationships.
  927. if ($book_tokens = token_find_with_prefix($tokens, 'book')) {
  928. $replacements += token_generate('menu-link', $book_tokens, array('menu-link' => $link), $options);
  929. }
  930. }
  931. }
  932. return $replacements;
  933. }
  934. /**
  935. * Implements hook_token_info() on behalf of menu.module.
  936. */
  937. function menu_token_info() {
  938. // Menu tokens.
  939. $info['types']['menu'] = array(
  940. 'name' => t('Menus'),
  941. 'description' => t('Tokens related to menus.'),
  942. 'needs-data' => 'menu',
  943. );
  944. $info['tokens']['menu']['name'] = array(
  945. 'name' => t('Name'),
  946. 'description' => t("The name of the menu."),
  947. );
  948. $info['tokens']['menu']['machine-name'] = array(
  949. 'name' => t('Machine-readable name'),
  950. 'description' => t("The unique machine-readable name of the menu."),
  951. );
  952. $info['tokens']['menu']['description'] = array(
  953. 'name' => t('Description'),
  954. 'description' => t('The optional description of the menu.'),
  955. );
  956. $info['tokens']['menu']['menu-link-count'] = array(
  957. 'name' => t('Menu link count'),
  958. 'description' => t('The number of menu links belonging to the menu.'),
  959. );
  960. $info['tokens']['menu']['edit-url'] = array(
  961. 'name' => t('Edit URL'),
  962. 'description' => t("The URL of the menu's edit page."),
  963. );
  964. $info['tokens']['menu-link']['menu'] = array(
  965. 'name' => t('Menu'),
  966. 'description' => t('The menu of the menu link.'),
  967. 'type' => 'menu',
  968. );
  969. $info['tokens']['menu-link']['edit-url'] = array(
  970. 'name' => t('Edit URL'),
  971. 'description' => t("The URL of the menu link's edit page."),
  972. );
  973. $info['tokens']['node']['menu-link'] = array(
  974. 'name' => t('Menu link'),
  975. 'description' => t("The menu link for this node."),
  976. 'type' => 'menu-link',
  977. );
  978. return $info;
  979. }
  980. /**
  981. * Implements hook_tokens() on behalf of menu.module.
  982. */
  983. function menu_tokens($type, $tokens, array $data = array(), array $options = array()) {
  984. $replacements = array();
  985. $url_options = array('absolute' => TRUE);
  986. if (isset($options['language'])) {
  987. $url_options['language'] = $options['language'];
  988. $language_code = $options['language']->language;
  989. }
  990. else {
  991. $language_code = NULL;
  992. }
  993. $sanitize = !empty($options['sanitize']);
  994. // Node tokens.
  995. if ($type == 'node' && !empty($data['node'])) {
  996. $node = $data['node'];
  997. foreach ($tokens as $name => $original) {
  998. switch ($name) {
  999. case 'menu-link':
  1000. if ($link = token_node_menu_link_load($node)) {
  1001. $replacements[$original] = $sanitize ? check_plain($link['title']) : $link['title'];
  1002. }
  1003. break;
  1004. }
  1005. // Chained token relationships.
  1006. if ($menu_tokens = token_find_with_prefix($tokens, 'menu-link')) {
  1007. if ($link = token_node_menu_link_load($node)) {
  1008. $replacements += token_generate('menu-link', $menu_tokens, array('menu-link' => $link), $options);
  1009. }
  1010. }
  1011. }
  1012. }
  1013. // Menu link tokens.
  1014. if ($type == 'menu-link' && !empty($data['menu-link'])) {
  1015. $link = (array) $data['menu-link'];
  1016. foreach ($tokens as $name => $original) {
  1017. switch ($name) {
  1018. case 'menu':
  1019. if ($menu = menu_load($link['menu_name'])) {
  1020. $replacements[$original] = $sanitize ? check_plain($menu['title']) : $menu['title'];
  1021. }
  1022. break;
  1023. case 'edit-url':
  1024. $replacements[$original] = url("admin/structure/menu/item/{$link['mlid']}/edit", $url_options);
  1025. break;
  1026. }
  1027. }
  1028. // Chained token relationships.
  1029. if (($menu_tokens = token_find_with_prefix($tokens, 'menu')) && $menu = menu_load($link['menu_name'])) {
  1030. $replacements += token_generate('menu', $menu_tokens, array('menu' => $menu), $options);
  1031. }
  1032. }
  1033. // Menu tokens.
  1034. if ($type == 'menu' && !empty($data['menu'])) {
  1035. $menu = (array) $data['menu'];
  1036. foreach ($tokens as $name => $original) {
  1037. switch ($name) {
  1038. case 'name':
  1039. $replacements[$original] = $sanitize ? check_plain($menu['title']) : $menu['title'];
  1040. break;
  1041. case 'machine-name':
  1042. // This is a machine name so does not ever need to be sanitized.
  1043. $replacements[$original] = $menu['menu_name'];
  1044. break;
  1045. case 'description':
  1046. $replacements[$original] = $sanitize ? filter_xss($menu['description']) : $menu['description'];
  1047. break;
  1048. case 'menu-link-count':
  1049. $query = db_select('menu_links');
  1050. $query->condition('menu_name', $menu['menu_name']);
  1051. $query->addTag('menu_menu_link_count');
  1052. $count = $query->countQuery()->execute()->fetchField();
  1053. $replacements[$original] = (int) $count;
  1054. break;
  1055. case 'edit-url':
  1056. $replacements[$original] = url("admin/structure/menu/manage/" . $menu['menu_name'], $url_options);
  1057. break;
  1058. }
  1059. }
  1060. }
  1061. return $replacements;
  1062. }
  1063. /**
  1064. * Implements hook_token_info() on behalf of profile.module.
  1065. */
  1066. function profile_token_info() {
  1067. $info = array();
  1068. foreach (_token_profile_fields() as $field) {
  1069. $info['tokens']['user'][$field->token_name] = array(
  1070. 'name' => check_plain($field->title),
  1071. 'description' => t('@category @type field.', array('@category' => drupal_ucfirst($field->category), '@type' => $field->type)),
  1072. );
  1073. switch ($field->type) {
  1074. case 'date':
  1075. $info['tokens']['user'][$field->token_name]['type'] = 'date';
  1076. break;
  1077. }
  1078. }
  1079. return $info;
  1080. }
  1081. /**
  1082. * Implements hook_tokens() on behalf of profile.module.
  1083. */
  1084. function profile_tokens($type, $tokens, array $data = array(), array $options = array()) {
  1085. $replacements = array();
  1086. $sanitize = !empty($options['sanitize']);
  1087. $language_code = isset($options['language']) ? $options['language']->language : NULL;
  1088. if ($type == 'user' && !empty($data['user'])) {
  1089. $account = $data['user'];
  1090. // Load profile fields if this is the global user account.
  1091. // @see http://drupal.org/node/361471
  1092. // @see http://drupal.org/node/967330
  1093. if ($account->uid == $GLOBALS['user']->uid && isset($account->timestamp)) {
  1094. $profile_users = array($account->uid => $account);
  1095. profile_user_load($profile_users);
  1096. $account = $profile_users[$account->uid];
  1097. }
  1098. $profile_fields = _token_profile_fields();
  1099. foreach ($tokens as $name => $original) {
  1100. if (isset($profile_fields[$name]) && !empty($account->{$profile_fields[$name]->name})) {
  1101. $value = $account->{$profile_fields[$name]->name};
  1102. switch ($profile_fields[$name]->type) {
  1103. case 'textarea':
  1104. $replacements[$original] = $sanitize ? check_markup($value, filter_default_format($account), '', TRUE) : $value;
  1105. break;
  1106. case 'date':
  1107. $timestamp = gmmktime(0, 0, 0, $value['month'], $value['day'], $value['year']);
  1108. $replacements[$original] = format_date($timestamp, 'medium', '', NULL, $language_code);
  1109. break;
  1110. case 'url':
  1111. $replacements[$original] = $sanitize ? check_url($value) : $value;
  1112. break;
  1113. case 'checkbox':
  1114. // Checkbox field if checked should return the text.
  1115. $replacements[$original] = $sanitize ? check_plain($profile_fields[$name]->title) : $profile_fields[$name]->title;
  1116. break;
  1117. case 'list':
  1118. $value = preg_split("/[,\n\r]/", $value);
  1119. $value = array_map('trim', $value);
  1120. $value = implode(', ', $value);
  1121. // Intentionally fall through to the default condition.
  1122. default:
  1123. $replacements[$original] = $sanitize ? check_plain($value) : $value;
  1124. break;
  1125. }
  1126. }
  1127. }
  1128. // Chained token relationships.
  1129. foreach ($profile_fields as $field) {
  1130. if ($field->type == 'date' && isset($account->{$field->name}) && $field_tokens = token_find_with_prefix($tokens, $field->token_name)) {
  1131. $date = $account->{$field->name};
  1132. $replacements += token_generate('date', $field_tokens, array('date' => gmmktime(0, 0, 0, $date['month'], $date['day'], $date['year'])), $options);
  1133. }
  1134. }
  1135. }
  1136. return $replacements;
  1137. }
  1138. /**
  1139. * Fetch an array of profile field objects, keyed by token name.
  1140. */
  1141. function _token_profile_fields() {
  1142. $fields = &drupal_static(__FUNCTION__);
  1143. if (!isset($fields)) {
  1144. $fields = array();
  1145. $results = db_query("SELECT name, title, category, type FROM {profile_field}");
  1146. foreach ($results as $field) {
  1147. $field->token_name = token_clean_token_name($field->name);
  1148. $fields[$field->token_name] = $field;
  1149. }
  1150. }
  1151. return $fields;
  1152. }
  1153. /**
  1154. * Fetch an array of field data used for tokens.
  1155. */
  1156. function _token_field_info($field_name = NULL) {
  1157. $info = &drupal_static(__FUNCTION__);
  1158. if (!isset($fields)) {
  1159. if ($cached = cache_get('field:info', 'cache_token')) {
  1160. $info = $cached->data;
  1161. }
  1162. else {
  1163. $info = array();
  1164. $fields = field_info_fields();
  1165. $instances = field_info_instances();
  1166. $type_info = field_info_field_types();
  1167. $entity_info = entity_get_info();
  1168. foreach ($fields as $field) {
  1169. $key = $field['field_name'];
  1170. if (!empty($field['bundles'])) {
  1171. foreach (array_keys($field['bundles']) as $entity) {
  1172. // Make sure a token type exists for this entity.
  1173. $token_type = token_get_entity_mapping('entity', $entity);
  1174. if (empty($token_type)) {
  1175. continue;
  1176. }
  1177. $info[$key]['token types'][] = $token_type;
  1178. $info[$key] += array('labels' => array(), 'bundles' => array());
  1179. // Find which label is most commonly used.
  1180. foreach ($field['bundles'][$entity] as $bundle) {
  1181. // Field information will included fields attached to disabled
  1182. // bundles, so check that the bundle exists before provided a
  1183. // token for it.
  1184. // @see http://drupal.org/node/1252566
  1185. if (!isset($entity_info[$entity]['bundles'][$bundle])) {
  1186. continue;
  1187. }
  1188. $info[$key]['labels'][] = $instances[$entity][$bundle][$key]['label'];
  1189. $info[$key]['bundles'][$token_type][$bundle] = $entity_info[$entity]['bundles'][$bundle]['label'];
  1190. }
  1191. }
  1192. }
  1193. if (isset($info[$key])) {
  1194. $labels = array_count_values($info[$key]['labels']);
  1195. arsort($labels);
  1196. $info[$key]['label'] = check_plain(key($labels));
  1197. // Generate a description for the token.
  1198. $info[$key]['description'] = t('@type field.', array('@type' => $type_info[$field['type']]['label']));
  1199. if ($also_known_as = array_unique(array_diff($info[$key]['labels'], array($info[$key]['label'])))) {
  1200. $info[$key]['description'] .= ' ' . t('Also known as %labels.', array('%labels' => implode(', ', $also_known_as)));
  1201. }
  1202. }
  1203. }
  1204. drupal_alter('token_field_info', $info);
  1205. cache_set('field:info', $info, 'cache_token');
  1206. }
  1207. }
  1208. if (isset($field_name)) {
  1209. return isset($info[$field_name]) ? $info[$field_name] : FALSE;
  1210. }
  1211. return $info;
  1212. }
  1213. /**
  1214. * Implements hook_token_info_alter() on behalf of field.module.
  1215. *
  1216. * We use hook_token_info_alter() rather than hook_token_info() as other
  1217. * modules may already have defined some field tokens.
  1218. */
  1219. function field_token_info_alter(&$info) {
  1220. $fields = _token_field_info();
  1221. // Attach field tokens to their respecitve entity tokens.
  1222. foreach ($fields as $field_name => $field) {
  1223. foreach (array_keys($field['bundles']) as $token_type) {
  1224. // If a token already exists for this field, then don't add it.
  1225. if (isset($info['tokens'][$token_type][$field_name])) {
  1226. continue;
  1227. }
  1228. // Ensure the tokens exist.
  1229. if (!isset($info['types'][$token_type]) || !isset($info['tokens'][$token_type])) {
  1230. continue;
  1231. }
  1232. if ($token_type == 'comment' && $field_name == 'comment_body') {
  1233. // Core provides the comment field as [comment:body].
  1234. continue;
  1235. }
  1236. $info['tokens'][$token_type][$field_name] = array(
  1237. // Note that label and description have already been sanitized by _token_field_info().
  1238. 'name' => $field['label'],
  1239. 'description' => $field['description'],
  1240. 'module' => 'token',
  1241. );
  1242. }
  1243. }
  1244. }
  1245. /**
  1246. * Implements hook_tokens() on behalf of field.module.
  1247. */
  1248. function field_tokens($type, $tokens, array $data = array(), array $options = array()) {
  1249. $replacements = array();
  1250. $sanitize = !empty($options['sanitize']);
  1251. $langcode = isset($options['language']) ? $options['language']->language : NULL;
  1252. // Entity tokens.
  1253. if ($type == 'entity' && !empty($data['entity_type']) && !empty($data['entity']) && !empty($data['token_type'])) {
  1254. $entity_type = $data['entity_type'];
  1255. // The field API does weird stuff to the entity, so let's clone it.
  1256. $entity = clone $data['entity'];
  1257. // Reset the prepared view flag in case token generation is called from
  1258. // inside field_attach_view().
  1259. unset($entity->_field_view_prepared);
  1260. list(, , $bundle) = entity_extract_ids($entity_type, $entity);
  1261. $fields = field_info_instances($entity_type, $bundle);
  1262. foreach (array_keys($fields) as $field_name) {
  1263. // Do not continue if the field is empty.
  1264. if (empty($entity->{$field_name})) {
  1265. continue;
  1266. }
  1267. // Replace the [entity:field-name] token only if token module added this
  1268. // token.
  1269. if (isset($tokens[$field_name]) && _token_module($data['token_type'], $field_name) == 'token') {
  1270. $original = $tokens[$field_name];
  1271. $field_output = field_view_field($entity_type, $entity, $field_name, 'token', $langcode);
  1272. $field_output['#token_options'] = $options;
  1273. $field_output['#pre_render'][] = 'token_pre_render_field_token';
  1274. $replacements[$original] = drupal_render($field_output);
  1275. }
  1276. }
  1277. // Remove the cloned object from memory.
  1278. unset($entity);
  1279. }
  1280. return $replacements;
  1281. }
  1282. /**
  1283. * Pre-render callback for field output used with tokens.
  1284. */
  1285. function token_pre_render_field_token(&$elements) {
  1286. // Remove the field theme hook, attachments, and JavaScript states.
  1287. unset($elements['#theme']);
  1288. unset($elements['#states']);
  1289. unset($elements['#attached']);
  1290. // Prevent multi-value fields from appearing smooshed together by appending
  1291. // a join suffix to all but the last value.
  1292. $deltas = element_get_visible_children($elements);
  1293. $count = count($deltas);
  1294. if ($count > 1) {
  1295. $join = isset($elements['#token_options']['join']) ? $elements['#token_options']['join'] : ", ";
  1296. foreach ($deltas as $index => $delta) {
  1297. // Do not add a suffix to the last item.
  1298. if ($index < ($count - 1)) {
  1299. $elements[$delta] += array('#suffix' => $join);
  1300. }
  1301. }
  1302. }
  1303. return $elements;
  1304. }