workflow.features.inc 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. <?php
  2. /**
  3. * @file
  4. * Provides Features integration for Workflow using the CRUD API.
  5. *
  6. * As you will notice this file will only handle the <export> of Worflows,
  7. * including states and transitions. The <import> is handeled magically,
  8. * and all modifications are done in function Workflow::save().
  9. */
  10. define('WORKFLOW_FEATURES_AUTHOR_NAME', 'workflow_features_author_name');
  11. // Even if workflow Node is not enabled, Features may use Node API's type_maps.
  12. require_once dirname(__FILE__) . '/workflow.node.type_map.inc';
  13. /**
  14. * Default controller handling features integration.
  15. */
  16. class WorkflowFeaturesController extends EntityDefaultFeaturesController {
  17. /**
  18. * Generates the result for hook_features_export().
  19. */
  20. public function export($data, &$export, $module_name = '') {
  21. $pipe = parent::export($data, $export, $module_name);
  22. foreach ($data as $workflow_name) {
  23. if ($workflow = workflow_load_by_name($workflow_name)) {
  24. // Add dependency on workflow_node.
  25. if (count($workflow->getTypeMap())) {
  26. $export['dependencies']['workflownode'] = 'workflownode';
  27. }
  28. }
  29. }
  30. return $pipe;
  31. }
  32. /**
  33. * Generates the result for hook_features_export_render().
  34. *
  35. * This is a copy of the parent, adding 'system_roles'.
  36. * The workflow is imported in the target system with Workflow::save().
  37. */
  38. public function export_render($module, $data, $export = NULL) {
  39. $translatables = $code = array();
  40. $code[] = ' $workflows = array();';
  41. $code[] = '';
  42. foreach ($data as $identifier) {
  43. // Clone workflow to make sure changes are not propagated to original.
  44. if ($workflow = entity_load_single($this->type, $identifier)) {
  45. $this->export_render_workflow($workflow, $identifier, $code);
  46. }
  47. }
  48. $code[] = ' return $workflows;';
  49. $code = implode("\n", $code);
  50. $hook = isset($this->info['export']['default hook']) ? $this->info['export']['default hook'] : 'default_' . $this->type;
  51. return array($hook => $code);
  52. }
  53. /**
  54. * Renders the provided workflow into export code.
  55. *
  56. * @param Workflow $workflow
  57. * The workflow to export.
  58. * @param string $identifier
  59. * The unique machine name for the workflow in the export.
  60. * @param array $code
  61. * A reference to the export code array that will receive the output.
  62. */
  63. protected function export_render_workflow(Workflow $workflow, $identifier, array &$code) {
  64. // Make sure data is not copied to the database.
  65. $workflow = clone $workflow;
  66. $this->sanitize_workflow_for_export($workflow);
  67. // Make sure to escape the characters \ and '.
  68. // The following method has the advantage, that you can export with
  69. // features,
  70. // and later import without enabling Features in the target system.
  71. $workflow_export = addcslashes(entity_export($this->type, $workflow, ' '), '\\\'');
  72. $workflow_identifier = features_var_export($identifier);
  73. $code[] = " // Exported workflow: {$workflow_identifier}";
  74. $code[] = " \$workflows[{$workflow_identifier}] = entity_import('{$this->type}', '" . $workflow_export . "');";
  75. $code[] = ''; // Blank line
  76. }
  77. /**
  78. * Prepares the provided workflow for export.
  79. *
  80. * Removes serial IDs and replaces them with machine names.
  81. *
  82. * @param Workflow $workflow
  83. * The workflow to sanitize. The contents of this object are modified directly.
  84. */
  85. protected function sanitize_workflow_for_export(Workflow $workflow) {
  86. // Eliminate serial IDs in exports to prevent "Overridden" status.
  87. // We use machine names instead.
  88. unset($workflow->wid);
  89. // Add roles to translate role IDs on target system.
  90. $permission = NULL;
  91. // Get system roles.
  92. $workflow->system_roles = workflow_get_roles($permission);
  93. // Only export system roles for roles used by this workflow.
  94. $roles = array();
  95. foreach ($workflow->transitions as $id => $transition) {
  96. foreach ($transition->roles as $rid) {
  97. $roles[] = $rid;
  98. }
  99. }
  100. $roles = array_unique($roles);
  101. foreach ($workflow->system_roles as $id => $system_role) {
  102. if (!in_array($id, $roles)) {
  103. unset($workflow->system_roles[$id]);
  104. }
  105. }
  106. $sid_to_name_map = $this->pack_states($workflow);
  107. $this->pack_transitions($workflow, $sid_to_name_map);
  108. }
  109. /**
  110. * "Packs" the states in the provided workflow into an export-friendly format.
  111. *
  112. * @param Workflow $workflow
  113. * The workflow to pack. The contents of this object are modified directly.
  114. *
  115. * @return array
  116. * A map of the old state IDs to their new machine names.
  117. */
  118. protected function pack_states(Workflow $workflow) {
  119. $named_states = array();
  120. $sid_to_name_map = array();
  121. foreach ($workflow->states as $state) {
  122. /* @var WorkflowState $state */
  123. $name = $state->getName();
  124. $sid_to_name_map[$state->sid] = $name;
  125. // Eliminate serial IDs in exports to prevent "Overridden" status.
  126. // We use machine names instead.
  127. unset($state->sid);
  128. unset($state->wid);
  129. $named_states[$name] = $state;
  130. }
  131. ksort($named_states);
  132. // Identify states by machine name.
  133. $workflow->states = $named_states;
  134. return $sid_to_name_map;
  135. }
  136. /**
  137. * "Packs" the transitions in the provided workflow into an export-friendly format.
  138. *
  139. * @param Workflow $workflow
  140. * The workflow to pack. The contents of this object are modified directly.
  141. *
  142. * @param array $sid_to_name_map
  143. * The map of numeric state IDs to their machine names, for remapping sid
  144. * references.
  145. */
  146. protected function pack_transitions(Workflow $workflow, array $sid_to_name_map) {
  147. $named_transitions = array();
  148. foreach ($workflow->transitions as $transition) {
  149. /* @var WorkflowTransition $transition */
  150. $start_name = $sid_to_name_map[$transition->sid];
  151. $end_name = $sid_to_name_map[$transition->target_sid];
  152. $new_name = WorkflowConfigTransition::machineName($start_name, $end_name);
  153. $transition->name = $new_name;
  154. $transition->start_state = $start_name;
  155. $transition->end_state = $end_name;
  156. // Eliminate serial IDs in exports to prevent "Overridden" status.
  157. // We use machine names instead.
  158. unset($transition->wid);
  159. unset($transition->tid);
  160. unset($transition->sid);
  161. unset($transition->target_sid);
  162. $named_transitions[$new_name] = $transition;
  163. }
  164. ksort($named_transitions);
  165. // Identify transitions by new machine name.
  166. $workflow->transitions = $named_transitions;
  167. }
  168. /**
  169. * Revert this workflow, either creating the workflow new (if one with the
  170. * same machine name is not present), or updating the existing workflow.
  171. *
  172. * @param string $module
  173. * The name of the feature module whose components should be reverted.
  174. */
  175. function revert($module = NULL) {
  176. // Loads defaults from feature code.
  177. $defaults = workflow_get_defaults($module);
  178. if (!empty($defaults)) {
  179. foreach ($defaults as $machine_name => $entity) {
  180. workflow_revert($defaults, $machine_name);
  181. }
  182. }
  183. }
  184. }
  185. /**
  186. * Implements hook_features_COMPONENT_alter().
  187. *
  188. * Adds the corresponding Workflow to the WorkflowField.
  189. */
  190. function workflow_features_pipe_field_base_alter(&$pipe, $data, $export) {
  191. if (!empty($data)) {
  192. foreach ($data as $field_name) {
  193. // $info = field_info_field($field_name);
  194. $field = _workflow_info_field($field_name);
  195. if ($field['type'] == 'workflow') {
  196. // $field['settings']['wid'] can be numeric or named.
  197. $workflow = workflow_load_single($field['settings']['wid']);
  198. // Fields might reference missing workflows.
  199. if (!empty($workflow)) {
  200. $pipe['Workflow'][] = $workflow->name;
  201. }
  202. }
  203. }
  204. }
  205. }
  206. /**
  207. * Implements hook_features_api_alter().
  208. *
  209. * Ensures Workflow always fires last during rebuild, to ensure that roles
  210. * referenced by workflows to be loaded-in when features contain roles.
  211. */
  212. function workflow_features_api_alter(array &$components) {
  213. // FIXME: Why is Workflow the only features provider with an uppercase component name?
  214. $component_name = 'Workflow';
  215. if (isset($components[$component_name])) {
  216. $setting = $components[$component_name];
  217. unset($components[$component_name]);
  218. $components[$component_name] = $setting;
  219. }
  220. }