webform.tokens.inc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. <?php
  2. /**
  3. * @file
  4. * Builds placeholder replacement tokens for webform-related data.
  5. */
  6. /**
  7. * Implements hook_token_info().
  8. */
  9. function webform_token_info() {
  10. // Webform submission tokens.
  11. $info['types']['submission'] = array(
  12. 'name' => t('Submission'),
  13. 'description' => t('Tokens related to webform submissions.'),
  14. 'needs-data' => 'webform-submission',
  15. );
  16. $info['tokens']['submission']['serial'] = array(
  17. 'name' => t('Serial number'),
  18. 'description' => t('The serial number of this webform submission.'),
  19. );
  20. $info['tokens']['submission']['sid'] = array(
  21. 'name' => t('Submission ID'),
  22. 'description' => t('The unique indentifier for the webform submission.'),
  23. );
  24. $info['tokens']['submission']['access-token'] = array(
  25. 'name' => t('Access token'),
  26. 'description' => t('The security token used to gain access to this webform submission.'),
  27. );
  28. $info['tokens']['submission']['date'] = array(
  29. 'name' => t('Date submitted'),
  30. 'description' => t('The date the webform was first save as draft or completed.'),
  31. 'type' => 'date',
  32. );
  33. $info['tokens']['submission']['completed_date'] = array(
  34. 'name' => t('Date completed'),
  35. 'description' => t('The date the webform was first completed (not draft).'),
  36. 'type' => 'date',
  37. );
  38. $info['tokens']['submission']['modified_date'] = array(
  39. 'name' => t('Date modified'),
  40. 'description' => t('The date the webform was last saved (draft or completed).'),
  41. 'type' => 'date',
  42. );
  43. $info['tokens']['submission']['ip-address'] = array(
  44. 'name' => t('IP address'),
  45. 'description' => t('The IP address that was used when submitting the webform.'),
  46. );
  47. $info['tokens']['submission']['user'] = array(
  48. 'name' => t('Submitter'),
  49. 'description' => t('The user that submitted the webform result.'),
  50. 'type' => 'user',
  51. );
  52. $info['tokens']['submission']['url'] = array(
  53. 'name' => t('URL'),
  54. 'description' => t("Webform tokens related to the submission's URL."),
  55. 'type' => 'url',
  56. );
  57. $info['tokens']['submission']['edit-url'] = array(
  58. 'name' => t('Edit URL'),
  59. 'description' => t("Webform tokens related to the submission's Edit URL."),
  60. 'type' => 'url',
  61. );
  62. $info['tokens']['submission']['values'] = array(
  63. 'name' => t('Webform submission values'),
  64. 'description' => '<div>' . t('Webform tokens from submitted data. Replace the "?" with the "form key", including any parent form keys separated by colons. You can append:') . '</div><ul>' .
  65. '<li>' . t('the question key for just that one question (grid components).') . '</li>' .
  66. '<li>' . t('the option key for just that one option (grid and select components).') . '</li>' .
  67. '<li>' . t('<code>@token</code> for the value without the label (the default).', array('@token' => ':nolabel')) . '</li>' .
  68. '<li>' . t('<code>@token</code> for just the label.', array('@token' => ':label')) . '</li>' .
  69. '<li>' . t('<code>@token</code> for both the label and value together.', array('@token' => ':withlabel')) . '</li>' .
  70. '<li>' . t('<code>@token</code> for just the key in a key|label pair (grid and select components).', array('@token' => ':key')) . '</li></ul>',
  71. 'dynamic' => TRUE,
  72. );
  73. return $info;
  74. }
  75. /**
  76. * Implements hook_tokens().
  77. */
  78. function webform_tokens($type, $tokens, array $data = array(), array $options = array()) {
  79. static $recursion_level = 0;
  80. // Return early unless submission tokens are needed and there is a submission.
  81. if ($type != 'submission' || empty($data['webform-submission']) || !webform_variable_get('webform_token_access')) {
  82. return array();
  83. }
  84. // Generate Webform tokens.
  85. $replacements = array();
  86. // Prepare all the data that we will likely need more than once.
  87. $submission = $data['webform-submission'];
  88. $node = isset($data['node']) ? $data['node'] : node_load($submission->nid);
  89. $email = isset($data['webform-email']) ? $data['webform-email'] : NULL;
  90. $sanitize = !empty($options['sanitize']);
  91. $format = $sanitize ? 'html' : 'text';
  92. $url_options = isset($options['language']) ? $options['language'] : array('absolute' => TRUE);
  93. $language_code = isset($options['language']) ? $options['language']->language : NULL;
  94. $markup_components = array();
  95. // Markup components may use tokens when displayed. Displaying the tokens
  96. // requires rendering the components. Rendering a markup component can then
  97. // cause infinite recursion. To prevent this, markup components are omitted
  98. // from the rendered submission if recursion has been detected.
  99. if ($recursion_level) {
  100. $markup_components = array_keys(array_filter($node->webform['components'],
  101. function ($component) {
  102. return $component['type'] == 'markup';
  103. }));
  104. }
  105. $recursion_level++;
  106. // Replace individual tokens that have an exact replacement.
  107. foreach ($tokens as $name => $original) {
  108. switch ($name) {
  109. case 'serial':
  110. $replacements[$original] = $submission->serial ? $submission->serial : '';
  111. break;
  112. case 'sid':
  113. $replacements[$original] = $submission->sid ? $submission->sid : '';
  114. break;
  115. case 'access-token':
  116. $replacements[$original] = webform_get_submission_access_token($submission);
  117. break;
  118. case 'date':
  119. $replacements[$original] = format_date($submission->submitted, webform_variable_get('webform_date_type'), '', NULL, $language_code);
  120. break;
  121. case 'completed_date':
  122. if ($submission->completed) {
  123. $replacements[$original] = format_date($submission->completed, webform_variable_get('webform_date_type'), '', NULL, $language_code);
  124. }
  125. break;
  126. case 'modified_date':
  127. $replacements[$original] = format_date($submission->modified, webform_variable_get('webform_date_type'), '', NULL, $language_code);
  128. break;
  129. case 'ip-address':
  130. $replacements[$original] = $sanitize ? check_plain($submission->remote_addr) : $submission->remote_addr;
  131. break;
  132. case 'user':
  133. $account = user_load($submission->uid);
  134. $name = format_username($account);
  135. $replacements[$original] = $sanitize ? check_plain($name) : $name;
  136. break;
  137. case 'url':
  138. $replacements[$original] = $submission->sid ? url("node/{$node->nid}/submission/{$submission->sid}", $url_options) : '';
  139. break;
  140. case 'edit-url':
  141. $replacements[$original] = $submission->sid ? url("node/{$node->nid}/submission/{$submission->sid}/edit", $url_options) : '';
  142. break;
  143. case 'values':
  144. $excluded_components = isset($email['excluded_components']) ? $email['excluded_components'] : array();
  145. $excluded_components = array_merge($excluded_components, $markup_components);
  146. $submission_renderable = webform_submission_render($node, $submission, $email, $format, $excluded_components);
  147. $replacements[$original] = drupal_render($submission_renderable);
  148. break;
  149. }
  150. }
  151. // Webform submission tokens for individual components.
  152. if ($value_tokens = token_find_with_prefix($tokens, 'values')) {
  153. // Get the full submission renderable without $excluded_components so that
  154. // individually referenced values are available.
  155. $submission_renderable = webform_submission_render($node, $submission, $email, $format, $markup_components);
  156. $available_modifiers = array(
  157. 'label',
  158. 'withlabel',
  159. 'nolabel',
  160. 'key',
  161. );
  162. foreach ($node->webform['components'] as $cid => $component) {
  163. // Build the list of parents for this component.
  164. $parents = ($component['pid'] == 0) ? array($component['form_key']) : webform_component_parent_keys($node, $component);
  165. $parent_token = implode(':', $parents);
  166. foreach ($value_tokens as $name => $original) {
  167. if (strpos($name, $parent_token) !== 0) {
  168. // Token not found as a prefix or exact match for this component.
  169. // Token loop continue.
  170. continue;
  171. }
  172. // Drill down into the renderable to find the element.
  173. $display_element = $submission_renderable;
  174. foreach ($parents as $parent) {
  175. if (!isset($display_element[$parent])) {
  176. // Sometimes an element won't exist in the submission renderable
  177. // due to conditional logic. If not found, skip that element.
  178. // Token loop continue.
  179. continue 2;
  180. }
  181. $display_element = $display_element[$parent];
  182. }
  183. // Individual tokens always have access granted even if they're
  184. // not displayed when printing the whole renderable.
  185. $display_element['#access'] = TRUE;
  186. // For grid components, see if optional question key is present.
  187. $matched_token = $parent_token;
  188. if ($display_element['#webform_component']['type'] === 'grid') {
  189. list($question_key) = explode(':', substr($name, strlen($matched_token) + 1));
  190. if (strlen($question_key) && isset($display_element[$question_key]['#value'])) {
  191. // Generate a faux select component for this grid question.
  192. $select_component = _webform_defaults_select();
  193. $select_component['type'] = 'select';
  194. $select_component['nid'] = $display_element['#webform_component']['nid'];
  195. $select_component['name'] = $display_element['#grid_questions'][$question_key];
  196. $select_component['extra']['items'] = $display_element['#webform_component']['extra']['options'];
  197. $display_element = _webform_display_select($select_component, $display_element[$question_key]['#value'], $format);
  198. $display_element['#webform_component'] = $select_component;
  199. $matched_token .= ':' . $question_key;
  200. }
  201. }
  202. // For select components, see if the optional option key is present.
  203. if ($display_element['#webform_component']['type'] === 'select') {
  204. list($option_key) = explode(':', substr($name, strlen($matched_token) + 1));
  205. if (strlen($option_key) && strpos("\n" . $display_element['#webform_component']['extra']['items'], "\n" . $option_key . '|') !== FALSE) {
  206. // Return only this specified option and no other values.
  207. $display_element['#value'] = array_intersect($display_element['#value'], array($option_key));
  208. $matched_token .= ':' . $option_key;
  209. }
  210. }
  211. // Assume no modifier (implied 'nolabel').
  212. $modifier = NULL;
  213. if (strcmp($name, $matched_token) !== 0) {
  214. // Check if this matches the key plus a modifier.
  215. $modifier = substr($name, strrpos($name, ':') + 1);
  216. // @todo: Allow components to provide additional modifiers per
  217. // type, i.e. key, day, hour, minute, etc.
  218. if (strcmp($name, $matched_token . ':' . $modifier) !== 0 || !in_array($modifier, $available_modifiers)) {
  219. // No match.
  220. // Token loop continue.
  221. continue;
  222. }
  223. }
  224. if ($modifier === 'label') {
  225. $replacements[$original] = webform_filter_xss($display_element['#title']);
  226. }
  227. elseif ($modifier === 'key' && $display_element['#webform_component']['type'] === 'select') {
  228. $values = array();
  229. foreach ($display_element['#value'] as $value) {
  230. $values[] = webform_filter_xss($value);
  231. }
  232. $replacements[$original] = implode(' ', $values);
  233. }
  234. else {
  235. // Remove theme wrappers for the nolabel modifier.
  236. if ($modifier === 'nolabel' || empty($modifier)) {
  237. $display_element['#theme_wrappers'] = array();
  238. }
  239. $replacements[$original] = render($display_element);
  240. }
  241. // Continue processing tokens in case another modifier is used.
  242. }
  243. }
  244. }
  245. // Chained token relationships.
  246. if ($date_tokens = token_find_with_prefix($tokens, 'date')) {
  247. $replacements += token_generate('date', $date_tokens, array('date' => $submission->submitted), $options);
  248. }
  249. if ($submission->completed && ($date_tokens = token_find_with_prefix($tokens, 'completed_date'))) {
  250. $replacements += token_generate('date', $date_tokens, array('date' => $submission->completed), $options);
  251. }
  252. if ($date_tokens = token_find_with_prefix($tokens, 'modified_date')) {
  253. $replacements += token_generate('date', $date_tokens, array('date' => $submission->modified), $options);
  254. }
  255. if (($user_tokens = token_find_with_prefix($tokens, 'user')) && $account = user_load($submission->uid)) {
  256. $replacements += token_generate('user', $user_tokens, array('user' => $account), $options);
  257. }
  258. if ($submission->sid) {
  259. if ($url_tokens = token_find_with_prefix($tokens, 'url')) {
  260. $replacements += token_generate('url', $url_tokens, array('path' => "node/{$node->nid}/submission/{$submission->sid}"), $options);
  261. }
  262. if ($url_tokens = token_find_with_prefix($tokens, 'edit-url')) {
  263. $replacements += token_generate('url', $url_tokens, array('path' => "node/{$node->nid}/submission/{$submission->sid}/edit"), $options);
  264. }
  265. }
  266. $recursion_level--;
  267. return $replacements;
  268. }