workflow.tokens.inc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. <?php
  2. /**
  3. * @file
  4. * Tokens hooks for Workflow module.
  5. *
  6. * To every entity type, a default Workflow token tree is added. To support
  7. * multiple tokens per entity bundle, an extra token tree 'Workflows' is
  8. * created.
  9. *
  10. * How to test?
  11. * - Enable module 'Token'; use page admin/help/token;
  12. * - Enable module 'Token example'; use page examples/token;
  13. * - Enable module Automatic Entity Label, set a label, and save entity.
  14. */
  15. /**
  16. * Implements drupal_alter('token_field_info', $info) from token.tokens.inc.
  17. *
  18. * It would be nice if this would work! But it doesn't...
  19. */
  20. /*
  21. // function workflow_token_field_info_alter(&$info) {
  22. // $workflow_field_info = _workflow_info_fields();
  23. // foreach($workflow_field_info as $field_name => $field_info) {
  24. // if (array_key_exists($field_name, $info)) {
  25. // $info[$field_name]['type'] = 'WorkflowLastTransition';
  26. // $info[$field_name]['module'] = 'workflow';
  27. // }
  28. // }
  29. // }
  30. */
  31. /**
  32. * Adds a subtree to each WorkflowField.
  33. *
  34. * ATM we only generate tokens for the last transition of a field.
  35. */
  36. function workflow_token_info_alter(&$data) {
  37. foreach ($data['tokens'] as $object => &$tokens) {
  38. // Add a token for scheduling, in 'seconds ago' format.
  39. if ($object == 'date' && !isset($tokens['seconds'])) {
  40. $tokens['seconds'] = array(
  41. 'name' => 'Seconds-since',
  42. 'description' => "A date in 'seconds ago' format (<i>604800</i>). Use it for easy scheduling workflow transitions.",
  43. 'module' => 'workflow',
  44. );
  45. }
  46. // High-jack the fields (they do not have sub-tokens, yet).
  47. foreach ($tokens as &$token) {
  48. // Caveat: the following algorithm is just a guess.
  49. if (isset($token['module']) && $token['module'] == 'token') {
  50. if (isset($token['description']) && 0 == substr_compare($token['description'], 'Workflow', 0, 8)) {
  51. $token['type'] = 'WorkflowTransition';
  52. $token['module'] = 'workflow';
  53. }
  54. }
  55. }
  56. }
  57. }
  58. /**
  59. * Implements hook_token_info().
  60. *
  61. * Adds tokens and token types, for Field, Workflow, State and Transition,
  62. * using the names of the entities from Workflow module.
  63. * Lots of tokens are already defined via entity_properties.
  64. * D7: a dependency on entity_token exists.
  65. *
  66. * @see workflow_entity_property_info_alter()
  67. */
  68. function workflow_token_info() {
  69. if (!module_exists('entity_token')) {
  70. return array();
  71. }
  72. /*
  73. * Token types.
  74. */
  75. $types['WorkflowField'] = array(
  76. 'name' => t('Workflows'),
  77. 'description' => t('Tokens related to workflows.'),
  78. 'needs-data' => 'entity',
  79. );
  80. $types['WorkflowTransition'] = array(
  81. 'name' => t('Workflow Transition'),
  82. 'description' => t('Tokens related to workflow transitions.'),
  83. // 'needs-data' => 'entity',
  84. 'needs-data' => 'WorkflowField',
  85. );
  86. $types['WorkflowState'] = array(
  87. 'name' => t('Workflow State'),
  88. 'description' => t('Tokens related to workflow state.'),
  89. 'needs-data' => 'WorkflowTransition',
  90. );
  91. $types['Workflow'] = array(
  92. 'name' => t('Workflow'),
  93. 'description' => t('Tokens related to workflows.'),
  94. 'needs-data' => 'WorkflowTransition',
  95. );
  96. /*
  97. * Chained tokens for nodes.
  98. */
  99. $last_transition = array(
  100. 'name' => t('Workflow last transition'),
  101. 'description' => t('Last workflow state transition of content.'),
  102. 'type' => 'WorkflowTransition',
  103. 'module' => 'workflow',
  104. );
  105. // Add a token tree to each core entity type. This allows easy reference
  106. // in the majority of cases.
  107. $workflow_field['last-transition'] = $last_transition;
  108. $entity['last-transition'] = $last_transition;
  109. $user['last-transition'] = $last_transition;
  110. $node['last-transition'] = $last_transition;
  111. $term['last-transition'] = $last_transition;
  112. $return = array(
  113. 'types' => $types,
  114. 'tokens' => array(
  115. // 'entity' => $entity, // #2272121
  116. 'user' => $user,
  117. 'node' => $node,
  118. 'term' => $term,
  119. 'WorkflowField' => $workflow_field,
  120. // 'WorkflowTransition' => $workflow_transition,
  121. // 'WorkflowState' => $workflow_state,
  122. // 'Workflow' => $workflow,
  123. ),
  124. );
  125. return $return;
  126. }
  127. /**
  128. * Implements hook_tokens().
  129. *
  130. * N.B. Keep the following functions aligned when changing properties:
  131. * - workflow_tokens()
  132. * - workflow_entity_property_info_alter()
  133. * - workflow_views_views_data_alter()
  134. */
  135. function workflow_tokens($type, $tokens, array $data = array(), array $options = array()) {
  136. $replacements = array();
  137. $sanitize = !empty($options['sanitize']);
  138. $langcode = isset($options['language']) ? $options['language']->language : NULL;
  139. // The 'node' tokens have already been replaced with 'entity'.
  140. // Skip for easier debugging.
  141. // @todo: is this always the case, or only if Entity Tokens is enabled?
  142. if ($type == 'node' || $type == 'user' || $type == 'term') {
  143. return $replacements;
  144. }
  145. if ($type == 'date' && !empty($data['date'])) {
  146. foreach ($tokens as $name => $original) {
  147. switch ($name) {
  148. case 'seconds':
  149. // This is our custom date token in 'seconds ago' format.
  150. $seconds = REQUEST_TIME - $data['date'];
  151. $replacements[$original] = $seconds;
  152. // Avoid reporcessing in the remainder of this function.
  153. break;
  154. }
  155. }
  156. return $replacements;
  157. }
  158. elseif ($type == 'entity' && !empty($data['entity'])) {
  159. // new, chained tokens, as of version 7.x-2.3.
  160. $entity = $data['entity'];
  161. $entity_type = $data['entity_type'];
  162. // $token_type = $data['token_type'];
  163. foreach ($tokens as $name => $original) {
  164. if (FALSE !== strpos($name, ':')) {
  165. // This is a chained property (contains ':').
  166. $name_parts = explode(':', $name);
  167. switch (end($name_parts)) {
  168. case 'comment':
  169. if (isset($entity->workflow_transitions) && isset($entity->workflow_transitions[$name_parts[0]])) {
  170. $replacements[$original] = $entity->workflow_transitions[$name_parts[0]]->$name_parts[1];
  171. }
  172. break;
  173. }
  174. }
  175. elseif (isset($data['workflow_token_type'])) {
  176. // This part taken from entity_token_tokens().
  177. $wrapper = entity_metadata_wrapper($data['workflow_token_type'], $data[$data['workflow_token_type']]);
  178. $property_name = str_replace('-', '_', $name);
  179. try {
  180. if ($name == 'workflow' ||
  181. $name == 'states' ||
  182. $name == 'transitions'
  183. ) {
  184. $replacement = _workflow_token_get_token($wrapper->$property_name, $options);
  185. }
  186. else {
  187. $replacement = _entity_token_get_token($wrapper->$property_name, $options);
  188. }
  189. if (isset($replacement)) {
  190. $replacements[$original] = $replacement;
  191. }
  192. } catch (EntityMetadataWrapperException $e) {
  193. // dpm('token not found: ' . $name);
  194. // If tokens for not existing values are requested, just do nothing.
  195. }
  196. }
  197. }
  198. // If this is a Last Transition, get subtokens for it.
  199. if ($sub_tokens = token_find_with_prefix($tokens, 'last-transition')) {
  200. // Get the workflow tokens from the transition of this entity.
  201. $transition = _workflow_tokens_get_transition($entity_type, $entity, NULL);
  202. $sub_data = array(
  203. 'WorkflowTransition' => $transition,
  204. 'workflow_token_type' => 'WorkflowTransition',
  205. );
  206. $sub_data += $data;
  207. $replacements += token_generate('entity', $sub_tokens, $sub_data, $options);
  208. }
  209. if (isset($data['WorkflowTransition'])) {
  210. // If this is a WorkflowField, get subtokens for it.
  211. $name_parts = explode(':', $name, 2);
  212. $field_name = reset($name_parts);
  213. if ($field_name != 'created'
  214. && isset($entity->{$field_name})
  215. && $sub_tokens = token_find_with_prefix($tokens, $field_name)
  216. ) {
  217. $transition = _workflow_tokens_get_transition($entity_type, $entity, $field_name);
  218. $sub_data = array(
  219. 'WorkflowTransition' => $transition,
  220. 'workflow_token_type' => 'WorkflowTransition',
  221. );
  222. $sub_data += $data;
  223. $replacements += token_generate('entity', $sub_tokens, $sub_data, $options);
  224. }
  225. $transition = $data['WorkflowTransition'];
  226. $field_name = $transition->field_name;
  227. if ($sub_tokens = token_find_with_prefix($tokens, 'Workflow')) {
  228. $sub_data = array(
  229. 'Workflow' => $transition->getWorkflow(),
  230. 'workflow_token_type' => 'Workflow',
  231. );
  232. $sub_data += $data;
  233. $replacements += token_generate('entity', $sub_tokens, $sub_data, $options);
  234. }
  235. // Unify to old-state, new-state.
  236. // Do not use underscores, or workflow_tokens will not work!
  237. if ($sub_tokens = token_find_with_prefix($tokens, 'old-state')) {
  238. $sub_data = array(
  239. 'WorkflowState' => $transition->getOldState(),
  240. 'workflow_token_type' => 'WorkflowState',
  241. );
  242. $sub_data += $data;
  243. $replacements += token_generate('entity', $sub_tokens, $sub_data, $options);
  244. }
  245. if ($sub_tokens = token_find_with_prefix($tokens, 'new-state')) {
  246. $sub_data = array(
  247. 'WorkflowState' => $transition->getNewState(),
  248. 'workflow_token_type' => 'WorkflowState',
  249. );
  250. $sub_data += $data;
  251. $replacements += token_generate('entity', $sub_tokens, $sub_data, $options);
  252. }
  253. if ($sub_tokens = token_find_with_prefix($tokens, 'user')) {
  254. if (isset($data['WorkflowTransition'])) {
  255. $sub_entity = $data['WorkflowTransition'];
  256. }
  257. $sub_data = array(
  258. 'entity_type' => 'user',
  259. 'user' => user_load($sub_entity->uid),
  260. 'token_type' => 'user',
  261. );
  262. $replacements += token_generate('user', $sub_tokens, $sub_data, $options);
  263. }
  264. if ($sub_tokens = token_find_with_prefix($tokens, 'created')) {
  265. $sub_data = array(
  266. 'entity_type' => 'date',
  267. 'date' => $data['WorkflowTransition']->stamp,
  268. 'token_type' => 'date',
  269. );
  270. $replacements += token_generate('date', $sub_tokens, $sub_data, $options);
  271. }
  272. }
  273. }
  274. return $replacements;
  275. }
  276. /**
  277. * Gets the token replacement by correctly obeying the options.
  278. *
  279. * Taken from _entity_token_get_token().
  280. */
  281. function _workflow_token_get_token($wrapper, $options) {
  282. // if ($wrapper->value() === NULL) {
  283. // // Do not provide a replacement if there is no value.
  284. // return NULL;
  285. // }
  286. if (empty($options['sanitize'])) {
  287. // When we don't need sanitized tokens decode already sanitizied texts.
  288. $options['decode'] = TRUE;
  289. }
  290. $langcode = isset($options['language']) ? $options['language']->language : LANGUAGE_NONE;
  291. // If there is a label for a property, e.g., defined by an options list or an
  292. // entity label, make use of it.
  293. if ($label = $wrapper->label()) {
  294. return empty($options['sanitize']) ? $label : check_plain(t($label, array(), array('langcode' => $langcode)));
  295. }
  296. switch ($wrapper->type()) {
  297. case 'Workflow':
  298. case 'WorkflowState':
  299. case 'WorkflowTransition':
  300. return $wrapper->value();
  301. }
  302. // Care for outputing list values.
  303. if ($wrapper instanceof EntityListWrapper) {
  304. $output = array();
  305. foreach ($wrapper as $item) {
  306. $output[] = _workflow_token_get_token($item, $options);
  307. }
  308. return implode(', ', $output);
  309. }
  310. // Else we do not have a good string to output, e.g., for struct values. Just
  311. // output the string representation of the wrapper.
  312. return (string) $wrapper;
  313. }
  314. /**
  315. * Helper function to get the Transition.
  316. *
  317. * If the node is being created or updated (using the NUMERIC id),
  318. * do not read from db, since the field widget has not been processed yet.
  319. * @todo: solve this with module weight?
  320. */
  321. function _workflow_tokens_get_transition($entity_type, $entity, $field_name) {
  322. global $user;
  323. $transitions = array();
  324. list($entity_id, , $entity_bundle) = entity_extract_ids($entity_type, $entity);
  325. $langcode = _workflow_metadata_workflow_get_properties($entity, array(), 'langcode', $entity_type, $field_name);
  326. if (!empty($entity_id) && !isset($entity->original)) {
  327. $transition = workflow_transition_load_single($entity_type, $entity_id, $field_name);
  328. return $transition;
  329. }
  330. // Get transition data from online-data.
  331. // Create dummy transitions, just to set $node->workflow_transitions[].
  332. foreach (_workflow_info_fields($entity, $entity_type, $entity_bundle) as $found_name => $field) {
  333. // If $field_name = NULL, any workflow_field/node is OK.
  334. if ($field_name <> NULL && $found_name != $field_name) {
  335. continue;
  336. }
  337. $old_sid = FALSE;
  338. $new_sid = $found_name ? _workflow_get_sid_by_items($entity->{$found_name}[$langcode]) : $entity->workflow_sid;
  339. if (!isset($entity_id)) {
  340. // Creating an entity.
  341. // When creating a node, and only 1 valid sid is available, then the
  342. // widget is not shown. This generates a problem, since the new/old
  343. // sid isn't yet loaded in the Entity.
  344. if ($new_state = workflow_state_load_single($new_sid)) {
  345. $workflow = $new_state->getWorkflow();
  346. $old_sid = $workflow->getCreationSid();
  347. }
  348. else {
  349. // $field['settings']['wid'] can be numeric or named.
  350. $workflow = workflow_load_single($field['settings']['wid']);
  351. $old_sid = $workflow->getCreationSid();
  352. $new_sid = $workflow->getFirstSid($entity_type, $entity, $field_name, $user, FALSE);
  353. }
  354. }
  355. elseif (isset($entity->original)) {
  356. if ($field_name && !isset($entity->original->{$found_name}[$langcode])) {
  357. // When updating a node, that did not have a workflow attached before.
  358. // (Happens when you add workflows to existing Entity types.)
  359. $old_sid = workflow_node_previous_state($entity, $entity_type, $found_name);
  360. }
  361. elseif ($field_name) {
  362. // Updating an entity.
  363. $old_sid = _workflow_get_sid_by_items($entity->original->{$found_name}[$langcode]);
  364. }
  365. else {
  366. $old_sid = workflow_node_previous_state($entity, $entity_type, $found_name);
  367. }
  368. }
  369. $transition = new WorkflowTransition();
  370. $transition->setValues($entity_type, $entity, $field_name, $old_sid, $new_sid, $user->uid, REQUEST_TIME, '');
  371. // Store the transition, so it can be easily fetched later on.
  372. // Store in an array, to prepare for multiple workflow_fields per entity.
  373. // This is a.o. used in hook_entity_update to trigger 'transition post'.
  374. $transitions[$field_name] = $transition;
  375. }
  376. $transition = ($field_name && isset($transitions[$field_name])) ? $transitions[$field_name] : reset($transitions);
  377. return $transition;
  378. }