Browse Source

updated workflow module to last dev

Bachir Soussi Chiadmi 7 years ago
parent
commit
a44ba1e90b
100 changed files with 15023 additions and 3961 deletions
  1. 559 1
      sites/all/modules/contrib/admin/workflow/CHANGELOG.txt
  2. 0 0
      sites/all/modules/contrib/admin/workflow/LICENSE.txt
  3. 74 23
      sites/all/modules/contrib/admin/workflow/README.txt
  4. 2 2
      sites/all/modules/contrib/admin/workflow/UPDATE.txt
  5. 811 0
      sites/all/modules/contrib/admin/workflow/includes/Entity/Workflow.php
  6. 192 0
      sites/all/modules/contrib/admin/workflow/includes/Entity/WorkflowConfigTransition.php
  7. 180 0
      sites/all/modules/contrib/admin/workflow/includes/Entity/WorkflowInterface.php
  8. 165 0
      sites/all/modules/contrib/admin/workflow/includes/Entity/WorkflowScheduledTransition.php
  9. 607 0
      sites/all/modules/contrib/admin/workflow/includes/Entity/WorkflowState.php
  10. 737 0
      sites/all/modules/contrib/admin/workflow/includes/Entity/WorkflowTransition.php
  11. 15 0
      sites/all/modules/contrib/admin/workflow/includes/Entity/WorkflowTransitionController.php
  12. 64 0
      sites/all/modules/contrib/admin/workflow/includes/Field/WorkflowD7Base.php
  13. 107 0
      sites/all/modules/contrib/admin/workflow/includes/Field/WorkflowDefaultWidget.php
  14. 329 0
      sites/all/modules/contrib/admin/workflow/includes/Field/WorkflowItem.php
  15. 752 0
      sites/all/modules/contrib/admin/workflow/includes/Form/WorkflowTransitionForm.php
  16. 84 0
      sites/all/modules/contrib/admin/workflow/includes/Test/WorkflowUnitTest.test
  17. 250 23
      sites/all/modules/contrib/admin/workflow/workflow.api.php
  18. 75 0
      sites/all/modules/contrib/admin/workflow/workflow.block.inc
  19. 740 0
      sites/all/modules/contrib/admin/workflow/workflow.deprecated.inc
  20. 589 0
      sites/all/modules/contrib/admin/workflow/workflow.entity.inc
  21. 198 222
      sites/all/modules/contrib/admin/workflow/workflow.features.inc
  22. 298 0
      sites/all/modules/contrib/admin/workflow/workflow.form.inc
  23. 20 6
      sites/all/modules/contrib/admin/workflow/workflow.info
  24. 481 44
      sites/all/modules/contrib/admin/workflow/workflow.install
  25. 0 0
      sites/all/modules/contrib/admin/workflow/workflow.js
  26. 594 800
      sites/all/modules/contrib/admin/workflow/workflow.module
  27. 92 0
      sites/all/modules/contrib/admin/workflow/workflow.node.type_map.inc
  28. 162 171
      sites/all/modules/contrib/admin/workflow/workflow.pages.inc
  29. 587 0
      sites/all/modules/contrib/admin/workflow/workflow.test.inc
  30. 386 199
      sites/all/modules/contrib/admin/workflow/workflow.tokens.inc
  31. 62 49
      sites/all/modules/contrib/admin/workflow/workflow_access/workflow_access.features.inc
  32. 5 4
      sites/all/modules/contrib/admin/workflow/workflow_access/workflow_access.info
  33. 37 14
      sites/all/modules/contrib/admin/workflow/workflow_access/workflow_access.install
  34. 171 258
      sites/all/modules/contrib/admin/workflow/workflow_access/workflow_access.module
  35. 157 0
      sites/all/modules/contrib/admin/workflow/workflow_access/workflow_access.pages.inc
  36. 40 0
      sites/all/modules/contrib/admin/workflow/workflow_access/workflow_access.workflow.inc
  37. 37 37
      sites/all/modules/contrib/admin/workflow/workflow_actions/README.txt
  38. 8 5
      sites/all/modules/contrib/admin/workflow/workflow_actions/workflow_actions.info
  39. 14 0
      sites/all/modules/contrib/admin/workflow/workflow_actions/workflow_actions.install
  40. 260 143
      sites/all/modules/contrib/admin/workflow/workflow_actions/workflow_actions.module
  41. 167 0
      sites/all/modules/contrib/admin/workflow/workflow_admin_ui/includes/Entity/EntityWorkflowUIController.php
  42. 44 0
      sites/all/modules/contrib/admin/workflow/workflow_admin_ui/includes/Test/workflow_named_transitions.test
  43. 11 4
      sites/all/modules/contrib/admin/workflow/workflow_admin_ui/workflow_admin_ui.api.php
  44. 4 4
      sites/all/modules/contrib/admin/workflow/workflow_admin_ui/workflow_admin_ui.css
  45. 7 4
      sites/all/modules/contrib/admin/workflow/workflow_admin_ui/workflow_admin_ui.info
  46. 10 16
      sites/all/modules/contrib/admin/workflow/workflow_admin_ui/workflow_admin_ui.install
  47. 112 1307
      sites/all/modules/contrib/admin/workflow/workflow_admin_ui/workflow_admin_ui.module
  48. 100 0
      sites/all/modules/contrib/admin/workflow/workflow_admin_ui/workflow_admin_ui.page.labels.inc
  49. 54 0
      sites/all/modules/contrib/admin/workflow/workflow_admin_ui/workflow_admin_ui.page.permissions.inc
  50. 328 0
      sites/all/modules/contrib/admin/workflow/workflow_admin_ui/workflow_admin_ui.page.states.inc
  51. 222 0
      sites/all/modules/contrib/admin/workflow/workflow_admin_ui/workflow_admin_ui.page.transitions.inc
  52. 241 0
      sites/all/modules/contrib/admin/workflow/workflow_admin_ui/workflow_admin_ui.page.type_map.inc
  53. 276 0
      sites/all/modules/contrib/admin/workflow/workflow_admin_ui/workflow_admin_ui.page.workflow.inc
  54. 5 4
      sites/all/modules/contrib/admin/workflow/workflow_cleanup/workflow_cleanup.info
  55. 19 182
      sites/all/modules/contrib/admin/workflow/workflow_cleanup/workflow_cleanup.module
  56. 166 0
      sites/all/modules/contrib/admin/workflow/workflow_cleanup/workflow_cleanup.pages.inc
  57. 26 0
      sites/all/modules/contrib/admin/workflow/workflow_field/README.txt
  58. 314 0
      sites/all/modules/contrib/admin/workflow/workflow_field/workflowfield.field.inc
  59. 68 0
      sites/all/modules/contrib/admin/workflow/workflow_field/workflowfield.formatter.inc
  60. 16 0
      sites/all/modules/contrib/admin/workflow/workflow_field/workflowfield.info
  61. 37 0
      sites/all/modules/contrib/admin/workflow/workflow_field/workflowfield.install
  62. 24 0
      sites/all/modules/contrib/admin/workflow/workflow_field/workflowfield.module
  63. 79 0
      sites/all/modules/contrib/admin/workflow/workflow_field/workflowfield.widget.inc
  64. 2 0
      sites/all/modules/contrib/admin/workflow/workflow_node/README.txt
  65. 75 0
      sites/all/modules/contrib/admin/workflow/workflow_node/plugins/access/workflow_bundle.inc
  66. 82 0
      sites/all/modules/contrib/admin/workflow/workflow_node/plugins/access/workflow_state.inc
  67. 60 0
      sites/all/modules/contrib/admin/workflow/workflow_node/plugins/content_types/workflow_node_form.inc
  68. 13 0
      sites/all/modules/contrib/admin/workflow/workflow_node/workflownode.info
  69. 6 0
      sites/all/modules/contrib/admin/workflow/workflow_node/workflownode.install
  70. 358 0
      sites/all/modules/contrib/admin/workflow/workflow_node/workflownode.module
  71. 247 0
      sites/all/modules/contrib/admin/workflow/workflow_node/workflownode.tokens.inc
  72. 38 0
      sites/all/modules/contrib/admin/workflow/workflow_node/workflownode.workflow.inc
  73. 236 0
      sites/all/modules/contrib/admin/workflow/workflow_notify/workflow_notify.admin.inc
  74. 15 0
      sites/all/modules/contrib/admin/workflow/workflow_notify/workflow_notify.info
  75. 297 0
      sites/all/modules/contrib/admin/workflow/workflow_notify/workflow_notify.module
  76. 12 0
      sites/all/modules/contrib/admin/workflow/workflow_notify/workflow_notify_og/workflow_notify_og.info
  77. 29 0
      sites/all/modules/contrib/admin/workflow/workflow_notify/workflow_notify_og/workflow_notify_og.install
  78. 114 0
      sites/all/modules/contrib/admin/workflow/workflow_notify/workflow_notify_og/workflow_notify_og.module
  79. 4 4
      sites/all/modules/contrib/admin/workflow/workflow_revert/workflow_revert.info
  80. 66 59
      sites/all/modules/contrib/admin/workflow/workflow_revert/workflow_revert.module
  81. 76 0
      sites/all/modules/contrib/admin/workflow/workflow_revert/workflow_revert.pages.inc
  82. 14 0
      sites/all/modules/contrib/admin/workflow/workflow_rules/README.txt
  83. 65 0
      sites/all/modules/contrib/admin/workflow/workflow_rules/workflow_rules.field.inc
  84. 11 7
      sites/all/modules/contrib/admin/workflow/workflow_rules/workflow_rules.info
  85. 8 27
      sites/all/modules/contrib/admin/workflow/workflow_rules/workflow_rules.module
  86. 153 0
      sites/all/modules/contrib/admin/workflow/workflow_rules/workflow_rules.node.inc
  87. 156 0
      sites/all/modules/contrib/admin/workflow/workflow_rules/workflow_rules.rules-callback.inc
  88. 30 185
      sites/all/modules/contrib/admin/workflow/workflow_rules/workflow_rules.rules.inc
  89. 39 0
      sites/all/modules/contrib/admin/workflow/workflow_rules/workflow_rules.workflow.inc
  90. 6 5
      sites/all/modules/contrib/admin/workflow/workflow_search_api/workflow_search_api.info
  91. 10 14
      sites/all/modules/contrib/admin/workflow/workflow_search_api/workflow_search_api.module
  92. 3 3
      sites/all/modules/contrib/admin/workflow/workflow_vbo/README.txt
  93. 260 0
      sites/all/modules/contrib/admin/workflow/workflow_vbo/actions/given.action.inc
  94. 86 0
      sites/all/modules/contrib/admin/workflow/workflow_vbo/actions/next.action.inc
  95. 10 6
      sites/all/modules/contrib/admin/workflow/workflow_vbo/workflow_vbo.info
  96. 13 129
      sites/all/modules/contrib/admin/workflow/workflow_vbo/workflow_vbo.module
  97. 32 0
      sites/all/modules/contrib/admin/workflow/workflow_views/handlers/workflow_views_handler_argument_state.inc
  98. 49 0
      sites/all/modules/contrib/admin/workflow/workflow_views/handlers/workflow_views_handler_field_comment_link_edit.inc
  99. 31 0
      sites/all/modules/contrib/admin/workflow/workflow_views/handlers/workflow_views_handler_field_node_link_workflow.inc
  100. 46 0
      sites/all/modules/contrib/admin/workflow/workflow_views/handlers/workflow_views_handler_field_sid.inc

+ 559 - 1
sites/all/modules/contrib/admin/workflow/CHANGELOG.txt

@@ -51,7 +51,7 @@ Issue #1468810 Timezones in scheduler
 Issue #1426844 by Bastlynn: Adding catch re: propery of last_history object being empty when node is not a workflow content type.
 Issue #1424008 by Bastlynn: Accounting for trying to go to views pg with workflow_views active, but no workflows in system.
 Issue #1400352 by Bastlynn: Fixing an error appearing udner E_STRICT in admin UI re: creating a default object.
-Something got wierd betwen the push/pull for this commit so trying that again.
+Something got weird between the push/pull for this commit so trying that again.
 Issue #1386430 by dylanhuang (patch by dozymoe): Views filter shows unknown when state selected in multi-workflow environment.
 Issue #1386430 by dylanhuang (patch by dozymoe): Views filter shows unknown when state selected in multi-workflow environment.
 Correcting for doubled ,, in install schema.
@@ -149,3 +149,561 @@ Issue #1993408 by NancyDru: Default new states to active.
 Issue #1542188 by justanothermark: Fix workflow display.
 Issue #2027507 by NancyDru: Fix sorting issue on history page.
 Issue #1993408 by NancyDru: Sysid not always set correctly.
+
+Since 7.x-1.2
+Issue #2037459 by johnv: Fix typo.
+Issue #2037531 by johnv, NancyDru: Fix choices return value.
+Issue #2039859 by Scaythe: Fix double encoding.
+Issue #2043667 by rodrigoeg, johnv: Fix typo in tokens.
+Issue #2044199 by justanothermark: Fix Feature reversion on non-existent states.
+Issue #2019345 by NancyDru: Allow hooks to reside in separate file.
+Issue #2052707 by NancyDru: Fix typo in single state value.
+Issue #2056773 by ambientdrup: Fix typo in VBO.
+Issue #2041329 by johnv: Changed api: added workflow_get_workflows_by_type(), for shorter code.
+Issue #2071663 by johnv: Changed function workflow_node_tab_access to gain performance.
+Issue #2071733 by johnv: Changed determination of showing Form, with new function workflow_show_form.
+Issue #2071733 by johnv: Preserve compatibility with workflow_extensions.
+Issue #2041319 by johnv: Added function workflow_get_first_state(), to have more unified code.
+Issue #2072059 by johnv: Changed call to hook_form_alter, by using hook_form_BASE_FORM_ID_alter, creating better performance.
+Issue #2072203 by johnv: Moved all node API functions from module file to node.inc file, to facilitate introduction of field API.
+Issue #2072081 by johnv: Fixed typo in workflow_node_tab_access().
+Issue #2077693 by johnv: Added Field API support in workflow_field_choices().
+Issue #2078785 by johnv: Added Field API support in workflow_node_tab_access().
+Issue #2079017 by johnv: Added Field API support for workflow_execute_transition().
+Issue #2080857 by johnv: Added Field API support for workflow_tab_page().
+Issue #2080915 by NancyDru: Signal Rules on reversion.
+Issue #2082225 by johnv: Changed interface of hook_workflow() (new optional parameter) to support Field API.
+Issue #2082247 by johnv: Added caching to workflow_field_choices().
+Issue #1357406 by pikku-h: Fixed Cron Scheduling keeps repeating if Old state and New state are the same.
+Issue #2019345 by johnv: Create a Workflow Field, with basic options.
+Issue #2087851 by ttkaminski: Fixed failing upgrade 7001 due to missing function: workflow_get_workflows().
+Issue #2042345 by arpeggio, rickdonohoe, johnv: Added dependency on "Views>3.3" to Workflow Views submodule.
+Issue #2019345 by johnv: Added helper function _workflow_get_sid_by_items() for Workflow Field.
+Issue #2094031 by johnv: Added return parameter to workflow_execute_transition().
+Issue #2019345 by johnv: Added classes for WorkflowDefaultWidget, WorkflowItem (Field type), Workflow, WorkflowState.
+Issue #2019345 by johnv: Fixed Field API: do not save node, when a scheduled state chage is posed on a comment.
+Issue #884788 by gdud, liquidcms: Fixed Views field "State: Current state name" isn't translated.
+Issue #2086287 by dooug: Fixed many typos in workflow and sub-modules.
+Issue #2086605 by johnv: Added settings to hide TimeZone for "Schedule for state change" option.
+Issue #2019345 by johnv: Fixed #states to hide/show scheduling info in Workflow Field Widget.
+Issue #2095467 by johnv: Changed flow logic to use new class WorkflowScheduledTransition.
+Issue #2095467 by johnv: Moved deprecated functions for class WorkflowScheduledTransition to new file workflow.deprecated.inc.
+Issue #2071733 by johnv: Changed workflow_show_form() to work with both old and new workflow objects.
+Issue #2019345 by johnv: Updated Workflow classes to show Workflow Form also on Node View page.
+Issue #2019345 by johnv: Fixed some issues in workflow.module file when using WorkflowField submodule.
+Issue #2019345 by johnv: Changed workflow_field submodule to support Workflow Form on Node view page.
+Issue #2019345 by johnv: Moved Node specific function workflow_forms() from workflow.module to workflow.node.inc.
+Issue #2019345 by johnv: Fixed WorkflowItem did not save tab_roles properly on Field Settings page.
+Issue #2019345 by johnv: Added some functions to the Workflow Entity classes.
+Issue #2019345 by johnv: Changed calls to Workflow object in various includes.
+Issue #2019345 by johnv: Added Workflow Field support in History tab page.
+Issue #2019345 by johnv: Added new WorkflowTransition class.
+Issue #2019345 by johnv: Moved Workflow Form processing code from workflow.module into new WorkflowTransition class.
+Issue #2019345 by johnv: Replaced all functional calls to Workflow object by OOP Methods of Workflow class.
+Issue #2019345 by johnv: Fixed wrong calls to Workflow::getFirstSid().
+Issue #2102405 by johnv: Added basic Entity support for Workflow Field.
+Issue #2102405 by johnv: Added Entity support for Workflow Scheduled Transitions.
+Issue #2019345 by johnv: Fixed caching for WorkflowState:getStates.
+Issue #2019345 by johnv: Changed workflow_node_view() to use Workflow classes.
+Issue #2102405 by johnv: Fixed indices for Workflow Scheduled Transitions.
+Issue #2019345 by johnv: Fixed order of States in Options list doesn't respect weight.
+Issue #2019345 by johnv: Added 'property_info' to Workflow Field for Entity/ Rules support.
+Issue #2019345 by johnv: Moved 'workflow_load' functon back to main module, since this is a menu callback.
+Issue #2019345 by johnv: Added 'grouped' options for Workflow::getOptions().
+Issue #2102663 by johnv: Added support for Workflow Field to workflow_rules sub-module.
+Issue #2111043 by johnv: Moved function workflow_field_choices() to WorkflowState->getOptions().
+Issue #2102663 by johnv: Fixed apparently rules functions must be in main include.
+Issue #2111795 by johnv: Fixed error when adding a Workflow with a name that is a translation of other workflow's name.
+Issue #2112263 by Nancydru: Typo in workflow_get_workflow)by_sid().
+Issue #2019345 by johnv: Fixed accidentally removed includes from workflow.module file.
+Issue #2019345 by johnv: Added extra methods to Workflow objects.
+Issue #2019345 by johnv: Added workflow_admin_ui now uses classes, too.
+Issue #2019345 by johnv: Added workflow.features.inc now includes workflow.deprecated.inc.
+Issue #2019345 by johnv: Added workflow.tokens.inc now uses classes, too.
+Issue #2019345 by johnv: Added extra methods to Workflow objects.
+Issue #2019345 by johnv: Added workflow_access now uses classes, too.
+Issue #2019345 by johnv: Added workflow_node_view now uses classes, too.
+Issue #2019345 by johnv: Added workflow_revert now uses classes.
+Issue #2019345 by johnv: Added workflow_search_api now uses classes.
+Issue #2019345 by johnv: Added workflow_vbo now uses classes.
+Issue #2019345 by johnv: Added workflow.install now uses classes.
+Issue #2019345 by johnv: Fixed error in WorkflowTransition->IsAllowed().
+Issue #2019345 by johnv: Added workflow_views now uses classes.
+Issue #2019345 by johnv: Fixed notice when trying to load not-existing Workflow or State.
+Issue #2019345 by johnv: Added workflow_cleanup now uses WorkflowState class.
+Issue #2019345 by johnv: Moved workflow_state functions to workflow.deprecated.inc, since they are all replaced by Workflow State class.
+Issue #2116179 by johnv: Moved workflow_type_map functions to own file workflow.node.type_map.inc.
+Issue #2019345 by johnv: Fixed notice on workflow_tab_form when a node type has both Workflow_node and Workflow_field enabled.
+Issue #2019345 by johnv: Moved code from workflow_admin_ui to Workflow->validate(), and added this check to Field settings form.
+Issue #2115199 by johnv: Moved pages from workflow_admin_ui.module to workfow_admin_ui.pages.inc, reducing footprint for all-but-these pages.
+Issue #2019345 by johnv: Added a create() function to classes Workflow and WorkflowState.
+Issue #2115199 by johnv: Some cosmetic changes in workfow_admin_ui.pages.inc.
+Issue #2019345 by johnv: Fixed notice when updating node in certain circumstances.
+Issue #2019345 by johnv: Changed function names in workflow_rules to reflect they are Node API only.
+Issue #2019345 by johnv: Fixed notice when an existing node does not have a workflow value yet.
+Issue #1036580 by johnv: Fixed error in Workflow Node API on Node View page when another workflow was assigned to node type.
+Issue #2119921 by johnv: Added dependency to Entity API module.
+Issue #2102193 by johnv: Fixed documentation of hook_workflow() in workflow.api.inc
+Issue #2019345 by johnv: Moved workflow_field_choices() to deprecated.inc, since it is completely replaced by WorkflowState->getOptions($node).
+Issue #2019345 by johnv: Fixed messages from PAReview.sh.
+Issue #2119921 by johnv: Moved workflow_node db functions to workflow.node.inc file.
+Issue #2119921 by johnv: Moved workflow_requirements() to workflow.install file.
+Issue #2019345 by johnv: Fixed messages from PAReview.sh.
+Issue #2122541 by johnv: Added new submodule Workflow Node.
+Issue #2122541 by johnv: Moved workflow_node-specific code from WorkflowState::deactivate() to hook workflownode_workflow('state delete').
+Issue #2122541 by johnv: Changed call to workflow_node specific function in worflow_admin_ui.
+Issue #2019345 by johnv: Fixed error when adding node after PAreview.
+Issue #2122541 by johnv: Moved workflow_node-specific code from Workflow::delete() to hook workflownode_workflow('worflow delete').
+Issue #2019345 by johnv: Removed duplication of Workflow*Transition::__construct.
+Issue #2019345 by johnv: Fixed duplicate Workflow Form on Node View page.
+Issue #2019345 by johnv: Moved saving of workflow_node_history to WorkflowTransition class.
+Issue #2019345 by johnv: Fixed error when submitting a Workflow Field on a comment.
+Issue #2102405 by johnv: Added Entity support for Workflow Executed Transitions (workflow_node_history).
+Issue #2019345 by johnv: Changed interface of workflow_node_current_state() for Field API.
+Issue #2128423 by johnv: Moved page code of workflow_cleanup to own file workflow_cleanup.pages.inc.
+Issue #2019345 by johnv: Moved workflow_updates_workflows() to workflow.deprecated.inc.
+Issue #2019345 by johnv: Moved page code of workflow_revert to own file workflow_revert.pages.inc.
+Issue #2019345 by johnv: Changed interface of workfow_execute_transition() while centralizing code for Field API and Node API.
+Issue #2115199 by johnv: Moved workflow_admin_ui type_map page to own file, (since it is only valid for Node API) and fixed PAReview errors.
+Issue #2019345 by johnv: Moved code to WorkflowState::create() to hide State specific details.
+Issue #2019345 by johnv: Removed dependency on workflow.node.inc in common code.
+Issue #2102405 by johnv: Changed interface of workflow_tab_access, workflow_tab_page to be $entity_type resistant.
+Issue #2019345 by johnv: Moved some code in workflow.module.
+Issue #2019345 by johnv: Fixed workflow_revert now supports all entity types and field names.
+Issue #2019345 by johnv: Changed theme_workflow_history_table to add proper 'Operations column' to history table.
+Issue #2019345 by johnv: Updated workfow_rules to reflect latest code. Still only supports 'node'.
+Issue #2019345 by johnv: Changed constructor of Workflow to be protected.
+Issue #2102193 by johnv: Changed call to 'hook_workflow', 'transition permitted' - arguments were swapped.
+Issue #2019345 by johnv: Fixed 'force' parameter in call to hook_workflow.
+Issue #2019345 by johnv: Added some love to the Workflow Form: less space for schedule block.
+Issue #2019345 by johnv: Added some love to the Workflow Form.
+Issue #2019345 by johnv: Added some Node/Field API dependent flow logic.
+Issue #2137149 by johnv: Changed Workflow History Tab to show workflow_transition_form instead of workflow_node_form.
+Issue #2137149 by johnv: Changed Node View page to show workflow_transition_form instead of workflow_node_form.
+Issue #2137149 by johnv: Removed workflow_tab_form code, since it is replaced by workflow_transition_form.
+Issue #2137219 by johnv: Changed workflow_actions to support Workflow Field.
+Issue #2122541 by johnv: Fixed strict warnings in WorkflowScheduledTransition.
+Issue #2137149 by johnv: Changed Node Edit page to show workflow_transition_form instead of workflow_node_form.
+Issue #2019345 by johnv: Fixed notices on WorkflowDefaultWidget.
+Issue #2137149 by johnv: Changed Node Comment form to show workflow_transition_form instead of workflow_node_form.
+Issue #2137149 by johnv: Changed code to use workflow_state_formatter to show Current State on any form.
+Issue #2137149 by johnv: Removed workflow_node_form: use workflow_transition_form instead.
+Issue #2019345 by johnv: Fixed enable changing nodes on front page.
+Issue #2019345 by johnv: Fixed expand schedule block when +1 forms on a page.
+Issue #2120451 by Kirsten Pol: Fixed superfluous sort when loading workflow_history_node.
+Issue #212429 by johnv: Fixed do not revert to deleted, disabled, same state in workflow_revert.
+
+Since 7.x-2.0-beta1
+Issue #2141763 by johnv: Fixed avoid calling hook_worflow when saving an Entity and state has not changed.
+Issue #1408398 by johnv: Fixed incorrect field type for 'sid' in table workflow_access.
+Issue #2142659 by johnv: Changed path of workflow_access, to be in sync with other submodules.
+Issue #2142677 by johnv: Changed location of workflow_access Admin UI page to own file.
+Issue #0437874 by hefox, johnv, e.a.: Fixed hide workflow tab and 'author' transitions for anonymous users on nodes with anonymous author.
+Issue #0437874 by hefox, johnv, e.a.: Fixed access grants in workflow_access for anonymous users on nodes with anonymous author.
+Issue #2019345 by johnv: Fixed in create form, Widget should show the first option, not the last.
+Issue #2121765 by johnv: Fixed show correct state in preview mode.
+Issue #2137219 by johnv: Fixed warning for workflow_actions.
+Issue #2146411 by johnv: Reverted cache for workflow_node_current_state.
+Issue #2137149 by johnv: Fixed double code in Transition->execute().
+Issue #2146411 by johnv: Fixed workflow_node_previous_state.
+Issue #2065799 by johnv: Added WorkflowState->count() to count nodes per State.
+Issue #2137149 by johnv: Changed 'workflow_options' to 'workflow_sid' in workflow_transition_form().
+Issue #2146411 by johnv: Fixed old&new state in Rules integration.
+Issue #2137149 by johnv: Fixed constructor of WorkflowTransition.
+Issue #2137149 by johnv: Added README.txt for Workflow_rules.
+Issue #2115671 by johnv: Changed workflow_views to Views 3 API structure.
+Issue #2019345 by johnv: Fixed selection of Options in Workflow object.
+Issue #658880 by johnv: Added filter on CreationState and Workflow in worklow_views_handler_filter_sid.
+Issue #2137149 by johnv: Fixed constructor of WorkflowTransition.
+Issue #1021126 by johnv: Fixed 'Workflow: state' Views Filter doesn't handle nodes without a workflow state.
+Issue #2147511 by johnv: Removed Views filters/fields/etc for {workflow_node} when module Worflow Node disabled.
+Issue #2147535 by johnv: Added Views filter and field for 'Transition: old sid', to complent existing 'new sid'.
+Issue #2147535 by johnv: Added More Views filter/field/sort for 'Workflow: current transition'.
+Issue #2147673 by johnv: Marked 'workflow: states' field and sort as deprecated: should be implemented using a relationship.
+Issue #2019345 by johnv: Renamed _workflow_view_workflow_get_state_name() to workflow_get_sid_label().
+Issue #2130783 by johnv: Added formatter 'count entities with this state' to 'Workflow: state' Views Field.
+Issue #2019345 by johnv: Added workflow_get_wid_label().
+Issue #2019345 by johnv: Fixed workflow_get_states() in workflow_admin_ui.
+Issue #1589254 by fedia.io1: Fixed Rename D6 hook_features_export_rebuild() to D7 hook_features_rebuild().
+Issue #2019345 by johnv: Renamed Workflow->getOptions() to workflow_get_state_names().
+Issue #2019345 by johnv: Fixed workflow.features.inc helper functions.
+Issue #2124915 by fedia.io1, Liam Mitchell: Fixed Transition's target_state and roles aren't exported with features.
+Issue #2126867 by slite: Fixed Reverting features causes warning.
+Issue #2147781 by zihong0: Fixed error, due to typo in workflow_acces.module.
+Issue #2144817 by zihong0: Fixed PHP 5.3/5.4 'Warning: Parameter 1' in workflow_transition_form().
+Issue #2019345 by johnv: Renamed Workflow*->get*ByName() to Workflow*->loadByName().
+Issue #2124915 by johnv: Fixed transfer transitions with Features.
+Issue #2148851 by johnv: Fixed transfer 'tab_roles' using machine names with Features.
+Issue #2148851 by brad.bulger, johnv: Fixed warning in Tokens when an old_state is deleted.
+
+Since 7.x-2.0-beta2
+Issue #2149027 by johnv: Fixed warning in workflow.rules.inc for PHP5.3/PHP5.4.
+Issue #2149121 by johnv: Restructured workflow_views_views_data().
+Issue #2149223 by johnv: Moved calls to Transition->isAllowed() into Transition->execute(), removing duplicate code.
+Issue #2149451 by hckurniawan: Fixed PHP Warnings in Drupal's core install.inc in distribution.
+Issue #2124429 by johnv: Removed obsolete code comment in workflow_revert.
+Issue #2019345 by johnv: Removed obsolete code comment in Workflow* classes.
+Issue #2149595 by johnv: Fixed Scheduled transitions are invisible, after upgrade to 7.x-2.0-beta2.
+Issue #2149671 by johnv: Added dependency on Workflow Node for workflow_search_api.
+Issue #2152425 by Propaganistas: Fixed error in WorkflowD7Base::__construct() when using custom entities.
+Issue #2019345 by johnv: Fixed workflow_revert now supports $entity_types and $field_name in hook 'transition permitted'.
+Issue #2153915 by johnv: Fixed incorrect allowed choices with $force == TRUE and hook 'transition permitted'.
+Issue #2019345 by johnv: Fixed flow logic, help texts, warnings of workflow_admin_ui.
+Issue #2019345 by johnv: Fixed return array in Workflow*Transition::load().
+Issue #0934078 by johnv: Added ability to bulk schedule in workflow_vbo, re-using standard workflow form.
+Issue #2019345 by johnv: Renamed workflow_save_transition() to workflow_transition_form_submit().
+Issue #2153529 by johnv: Fixed PDO exception upon node_save().
+Issue #0934078 by johnv: Fixed ability force a bulk state change workflow_vbo, re-using standard workflow form.
+Issue #2156737 by johnv: Fixed error when entering an invalid time in the workflow form.
+Issue #0410538 by johnv: Added Views 'workflow' link, analog to the existing 'edit' link.
+Issue #2100445 by johnv: Fixed reassigning content to disabled state in workflow_admin_ui.
+Issue #2156737 by johnv: Reverted part of patch.
+Issue #2149121 by zihong0: Fixed warnings from workflow_views.
+Issue #2157987 by zihong0: Added admin_summary in Views State filter handler.
+Issue #0658880 by johnv: Fixed warnings and bad behaviour when setting SID filter.
+Issue #2153529 by johnv: Fixed PDOException when saving new node.
+Issue #2100445 by johnv: Fixed reassigning content to disabled state in workflow_admin_ui -2-.
+Issue #2162333 by johnv: Fixed messages when adding/saving non-nodes with workflow_access enabled.
+Issue #2162333 by johnv: Added message in modules page 'workflow_access works only for nodes'.
+Issue #1212614 by johnv: State is changed twice - only when adding a comment during state change.
+Issue #2019345 by johnv: Added notices in info-file for better usage of submodules.
+Issue #2019345 by johnv: Added WorkflowConfigTransition as an Entity for workflow_transitions.
+Issue #2019345 by johnv: Fixed delete transitions even when no content is assigned.
+Issue #2019345 by johnv: Moved workflow_transitions functions to Workflow(ConfigTransition) classes.
+Issue #2163805 by johnv: Added user_access to workflow_revert link, not only page.
+Issue #2019345 by johnv: Fixed message on workflow_tab_page.
+Issue #2149223 by johnv: Moved code from workflow_allowable_transitions to central Workflow::getTransitions().
+Issue #2149223 by johnv: Moved code from workflow_transition_allowed to WorkflowConfigTransition::isAllowed().
+Issue #2165179 by colan: Fixed update of workflow_node_history.
+Issue #2160375 by Matthijs: Added workflow tab for all entities.
+Issue #2112199 by johnv: Fixed messages by PAReview.sh in WorkflowItem.php.
+Issue #2149223 by johnv: Moved code from workflow_transition_allowed to WorkflowConfigTransition::isAllowed() -2-.
+Issue #2019345 by johnv: Moved code to unify workflow_node and workflow_field.
+Issue #2019345 by johnv: Added function workflow_state_load.
+Issue #2165703 by Propaganistas, johnv: Fixed strict warnings.
+Issue #2166199 by zihong0: Fixed error.
+Issue #2166047 by johnv: Fixed error when choosing 'Options' widget directly on field-add page.
+Issue #2165807 by dcanetma: Fixed scheduled transitions cron is not working on time basis.
+Issue #2019345 by johnv: Fixed messages on workflow settings edit page.
+Issue #2064449 by johnv: Changed role ID of 'workflow author' from 'author' to '-1'; 'roles' fields are now serialized.
+Issue #2168571 by rv0: Fixed Workflow field required setting is not saved.
+Issue #2102765 by johnv: Fixed documentation of hook_workflow_operations.
+Issue #2169859 by johnv: Fixed problems with Features.
+Issue #2064449 by johnv: Changed hook_update_7007.
+Issue #2171615 by Tim-Erwin, johnv: Fixed wrong parsing of hook_workflow results.
+Issue #2019345 by johnv: Added back the type_map functions to main workflow module, to preserve compatibility with third-party modules.
+Issue #2170037 by johnv: Changed default widget to 'workflow' widget, instead of 'select'.
+Issue #2171615 by Tim-Erwin, johnv: Fixed wrong parsing of hook_workflow results. -2-
+Issue #2165349 by johnv: Added logging in hook_field_delete; added comments. Moved code of hook_node_delete to WorkflowItem.php
+Issue #1559680 by johnv: Changed Transitions into Entity. Views support enabled.
+Issue #2175867 by johnv: Updated workflow_views_handler_field_username to use all Views' optons.
+Issue #2064449 by johnv: Merged hook_update_7001 with hook_update_7007 to avoid errors in 7001.
+Issue #2019345 by johnv: Moved entity_<operations> function calls to workfow.entity.inc.
+Issue #2019345 by kaare: Fixed $node->name is unset in workflow.tokens.inc.
+Issue #2184633 by johnv: Fixed caching of Workflow (BTW, which is now a true entity).
+Issue #2019345 by johnv: Moved workflow.node.inc to submodule workflow_node.
+Issue #2187151 by johnv: Added an 'Actions buttons' widget to Workflow form for permitted states.
+Issue #2187151 by johnv: Added an 'Actions buttons' widget for permitted states on Workflow_node and Entity Form.
+Issue #2187677 by johnv, boris sondagh: Removed 'Sys ID' column in Admin UI states page.
+Issue #2185859 by johnv: Fixed 'Class not found' error in hook_update_7004.
+Issue #2192815 by johnv: Renamed, reordered private functions in workflow.features.inc
+Issue #2192815 by johnv: Fixed broken features export since Workflow is an entity.
+Issue #2189761 by johnv, zengenuity, naptown: Add Rules support for Workflow Field using 'EntityDefaultRulesController', removing custom code.
+Issue #2019345 by johnv: Fixed workflow history is saved twice when adding transition via comment.
+Issue #2187151 by johnv: Added an 'Actions buttons' widget to Workflow form on Comments and Entity form for Workflow Field .
+Issue #2019345 by johnv: Added workflow_enable() and cleared texts.
+Issue #2202405 by johnv: Added Rules action 'Set a workflow field state' to workflow_rules submodule.
+Issue #2196587 by johnv: Fixed earlier introduced error in workflow.tokens.inc.
+Issue #2196587 by johnv: Fixed Notice: Undefined property: stdClass::$workflow in workflow_tokens().
+Issue #2200089 by johnv: Fixed PDOException: SQLSTATE[42S22] upon 'transition delete' when workflow_actions enabled.
+Issue #2200089 by johnv: Fixed backwards compatibility with workflow_actions.
+Issue #2205707 by johnv: Moved some views handlers from workflow_extensions to workflow_views.
+Issue #2205707 by johnv: Moved workflow_comment_edit_link field handler from workflow_extensions to workflow_views.
+Issue #2208675 by johnv: Fixed Unnecessary db-calls upon node_load in workflow_node.
+Issue #2208675 by johnv: Shorter code in workflow_state_formatter().
+Issue #2019345 by johnv: Use api in workflow_search_api.
+Issue #2168197 by johnv: Added VBO support for workflow_field, preventing assigning a state of an illegal Workflow.
+Issue #2206923 by Uccio: Fixed Uninstall of workflow_actions doesn't remove actions.
+Issue #2208971 by johnv: Fixed refresh entity_cache upon saving new workflow_node state in WorkflowTransition->execute().
+Issue #2209359 by 2pha, johnv: Fixed workflow_access and other workflow_submodules when $field_name is not known.
+Issue #2207423 by johnv: Removed double lock on workflow_actions for workflow_field.
+Issue #2210787 by johnv: Fixed avoid calling 'transition post' twice when workflow_actions is enabled.
+Issue #2207423 by johnv: Fixed hook_workflow('transition post') is now called also for workflow_field on the correct moment.
+Issue #0843568 by johnv: Added 'revision_id' to {workflow_node_history} when saving a transition.
+Issue #2207423 by johnv: Fixed hook_workflow('transition post') is now called only in workflow_actions, avoiding extra hook in other cases.
+Issue #2212197 by johnv: Fixed access to workflow history tab per role.
+Issue #2102409 by johnv: Fixed multiple workflow_fields per bundle, when saving a new entity.
+Issue #2207423 by johnv: Fixed workflow_actions' hook_workflow('transition post') also for non-node entities.
+Issue #2102409 by johnv: Fixed multiple workflow_fields per bundle, in workflow_access.
+Issue #2216641 by johnv: Fixed notices by workflow_access when saving a node without workflow.
+Issue #2216685 by Bastlynn: Fixed WSOD.
+Issue #2216685 by Bastlynn: Fixed WSOD, part 2.
+Issue #2216685 by johnv: Further cleanup and unification of workflow_node and workflow_field.
+Issue #2116179 by johnv: Some more isolation of type_map functions.
+Issue #2216685 by johnv: Further cleanup for workflow_vbo.
+Issue #2168197 by johnv: Separated workflow_vbo actions into own files, for less footprint and better maintainability.
+Issue #2218303 by johnv: Fixed error if you try to assign a state to a non-relevant node type (e.g. in workflow_vbo action).
+
+Since 7.x-2.2
+Fixed some problems with WorkflowConfigTransition.
+Fixed breadcrumbs on workflow add page.
+Issue #2218303 by zihong0: Fixed type in workflow_node_previous_state.
+Issue #2226781 by johnv: Changed Admin UI to use Entity UI.
+Issue #2226781 by johnv: Added message when saving states.
+Issue #2226451 by johnv: Streamlined code around $state->showWidget().
+Issue #2226451 by johnv: Removed showing workflow widget when creating a node, and only 1 state allowed.
+Issue #2228317 by johnv: Moved hook_user_role_insert() since it was not called when Admin UI disabled.
+Issue #2220065 by zihong0, johnv: Fixed workflow_access_node_access_records() is not called from node_save().
+Issue #2228317 by johnv: Fixed hook_user_role_insert is not called when Admin UI disabled.
+Issue #2228457 by johnv: Do NOT enable all roles for Transitions, upon enabling Workflow UI module.
+Issue #2228457 by johnv: Fixed some help texts.
+Issue #2228457 by johnv: Fixed menu_build should not be called in admin_ui install, and is not needed since update_7001 exists.
+Issue #2226781 by johnv: Removed Workfow Add form, in favour of Workflow Edit form.
+Issue #2128423 by johnv: Fixed small theming thingy in workflow_cleanup.pages.inc.
+Issue #2187321 by johnv: Added Workflow as an Exportable entity (not complete yet, but allows cloning).
+Issue #2226451 by johnv: Fixed workflow widget when creating a node, and only 1 state allowed: should move node to first state.
+Issue #2226781 by johnv: Changed Workflow Access page to use Entity UI.
+Issue #2226781 by johnv: Changed Workflow Clean up page to use Entity UI.
+Issue #2220065 by zihong0, johnv: Fixed workflow_access_node_access_records() is not called from node_save().
+Issue #2220065 by johnv: Fixed better filters WorkflowTransition->execute().
+Issue #2209359 by johnv: Fixed submitting workflow access settings, does not trigger access rebuild for workflow_field.
+Issue #2209359 by johnv: Fixed better UX for workflow_access pages, and removing a hook for better performance.
+Issue #2230833 by johnv: Fixed deleting/fetching ScheduledTransitions, when having multiple workflow_fields per node.
+Issue #2226781 by johnv: Fixed parameters of workflow_admin_ui_edit_form_validate().
+Issue #2230833 by johnv: Fixed showing ScheduledTransitions, when having multiple workflow_fields per node.
+Issue #2226781 by johnv: Fixed screen flow of workflow_admin_ui_edit_form().
+Issue #2209359 by johnv: Fixed workflow_access_node_access_records() in case of Access Rebuild of Workflow Fields.
+Issue #2209359 by johnv: Fixed error message in workflow_access.
+Issue #2019345 by johnv: Added parameter 'transition' to hook_workflow().
+Issue #2019345 by johnv: Added error message when setting state on non-relevant entity.
+Issue #2209359 by johnv: Fixed error in workflow_actions with workflow_field and workflow_access enabled.
+Issue #2229635 by johnv: Fixed Undefined index: entity_type in workflow_vbo_next_state_action().
+Issue #2231903 by johnv: Fixed usage of field_info_field_map() under D7.22.
+Issue #2168197 by johnv: Fixed support of VBO 'modify entity values' for workflow_field.
+Issue #2102409 by johnv: Removed $workflow->getWorkfowItem() c.s., which is not needed anymore.
+Issue #1649216 by johnv: Changed display of 'author' and 'creation' on admin pages, to avoid confusion when 'autor' role is defined.
+Issue #2019345 by johnv: Added error message when used workflow is deleted.
+Issue #2237125 by johnv: Added token 'workflow-state-age' from workflow_extensions.
+Issue #2237125 by johnv: Reordered functions in workflow.tokens.inc.
+Issue #0907442 by johnv: Fixed comment field can now be hidden, optional and (new!) required.
+Issue #2239189 by johnv: Fixed Anonymous author cannot set a state for new content.
+Issue #2019345 by johnv: Removed workflownode.inc into workflownode.module.
+Issue #2164081 by johnv: Some nervous code reshuffling regarding unpublished nodes.
+Issue #2237361 by johnv: Fixed SQL error in node_access when Anonymous Author has workflow_access rights.
+Issue #2240009 by johnv: Changed some code in workflow.tokens.inc, to prepare for multiple workflows per entity.
+Issue #2019345 by johnv: Changed WorkflowState to an Entity, too.
+Issue #2019345 by johnv: Added method getWorkflow() to WorkflowTransition class.
+Issue #2241577 by johnv: Fixed export/import Features after making WorkflowState an entity.
+Issue #2164081 by johnv: Removed code that hides Workflow Form on unpublished nodes.
+Issue #2241577 by johnv: Removed dependency on Features module. Now you can enable a workflow feature without!
+Issue #2217291: Added new view + view_mode 'history_tab', to create customizable tab, for multiple workflows.
+Issue #0443094: Fixed typo in workflow_admin_paths_alter().
+Issue #2241577 by preddy: Added check for existing workflow in WorkflowConfigTransition::save().
+Issue #2243569: Added inline documentation for usage of workflow_get_workflows_by_type with multiple workflows per node type.
+Issue #2243997: Added refreshed default views in workflow_views under /workflow/ path.
+Issue #2217291: Fixed workflow_views for installations with D7<7.22.
+Issue #2240009: Moved current tokens to submodule 'workflownode' to prepare for workflow_field tokens.
+Issue #2240009: Added better readable 'chained' tokens, re-using tokens of core objects.
+Issue #2237125: Removed token 'workflow-state-age' from workflow_extensions, since it is now available as a chained token.
+Issue #2240009: Removed dpm().
+Issue #2226781: Fixed error on type_map admin page for PHP5.4
+Issue #2226781: Fixed warnings for PHP5.4 using reset().
+Issue #2241577: Added machine_name to Workflow, influencing features.
+Issue #2240009: Removed dpm().
+Issue #2240009: Fixed 'chained' tokens now also workf when saving entities.
+Issue #2226781: Fixed new states add added at end of state list.
+Issue #0073412: Fixed a long-standing issue by adding a machine_name and label for Transitions! Many thanks to workflow_named_transitions.
+Issue #0073412: Removed hardcoded text on action buttons, since they can be set with Transition labels, now.
+Issue #2019345: Fixed constructor of WorkflowConfigTransition.
+Issue #0744272: Added new hook to allow freely definable transitions per state.
+
+Since 7.x-2.3
+Issue #0744272: Fixed sort order of states in Workflow Form was not according to states weight.
+Issue #2102409: Fixed some function (calls) did not support multiple field_names.
+Issue #2250403: Fixed typo in workflow_access function, leaving Admin UI only accessible for user 1.
+Issue #2256607 by chadhester: Added 'bundle' parameter to workflow_get_workflows_by_type().
+Issue #2257145 by chadhester: Fixed Empty $wid value in _workflow_get_workflow_creation_sid().
+Issue #2259893 by preddy: Fixed incorrectly hidden labels on Workflow Labels page.
+Issue #2260183: Fixed errors with Tokens.
+Issue #2261375: Fixed Workflow label was replaced by Workflow machine_name in version 2.3
+Issue #2260183: Fixed errors with Tokens with entities that existed before Workflow was added to Entity type.
+Issue #2265147: Fixed error in workflow_update_7011() during update.php.
+Issue #2266077: Added help text on workflow_cleanup page.
+Issue #2267115: Fixed No watchdog call when using Workflow Field.
+Issue #2270581: Added examples of hook_workflow_(BASE_FORM_ID_)alter() to workflow.api.php.
+Issue #2272121: Fixed 'Tokens' warning on page admin/reports/status.
+Issue #2269703: Added code to create a workflow programmatically.
+Issue #2272761: Fixed wsod in version 2.4-beta1, due to dpm() leftover in tokens.inc.
+Issue #2276149: Fixed 'wid' value in Views Export of filter 'Workflow current: Current state' in workflow_views_handler_filter_sid.
+Issue #2263561: Added Entity properties + tokens.
+Issue #2277751: Added Field Properties for workflow_field.
+Issue #2265615: Added Rules Action 'Set a workflow field state' for any entity.
+Issue #2277935: Fixed message 'Wrong call to constructor' when rebuilding permission with workflow_access enabled.
+Issue #2196247: Added machine_names for WorkflowStates.
+Issue #2217291: Removed warnings from workflow_views, when another module was rudely disabled.
+Issue #2279241: Fixed json_encode() error by making $workflow property protected.
+Issue #2279677: Fixed Notice 'Undefined property: $title in WorkflowScheduledTransition->save()'.
+Issue #2279967: Fixed Wrong default value for ScheduledTransition widget.
+Issue #2282969: Added help text on Workflows page, to tell user how to use Workflow Field.
+Issue #2285327: Fixed wrong function call in VBO action 'Set to next state'.
+Issue #2277751 by barami: Added $property['options list'] to Workflow Field.
+Issue #1511694 by joel_osc: Added integration of WorkflowNode with Panels.
+Issue #2187731 by boris sondagh: Added Panels access/selection rules based on workflow.
+Issue #2152435: Prepared WorkflowField Features export for better import when wid changed in target system.
+Issue #2287057: Fixed usage of actual $user instead of global $user for Transition.
+Issue #2287057: Fixed undefined variable $workflow_id in workflow.module.
+Issue #2283821: Fixed error in hook_update_7007() due to schema changes, when Upgrading from old D6/D7-version to 7.x-2.x.
+Issue #2283821: Fixed error in workflow_node, when Upgrading from old D6/D7-version to 7.x-2.x.
+Issue #2291411: Fixed broken link on Admin UI.
+Issue #2217855: Fixed fatal error in workflow_actions when ConfigTransition has invalid target_state.
+Issue #2187321: Fixed message when importing workflow.
+Issue #2296843: Added more properties and functions to entities.
+Issue #2296843: Added more properties and functions to entities - remove typo.
+Issue #2205707: Added permission for workflow_comment_edit_link field handler from workflow_extensions.
+Issue #2211145: Grouped workflow action buttons together with Drupal buttons ([Save], [Preview]).
+Issue #2283821: Fixed error in hook_update_7007() due to schema changes, when Upgrading from old D6/D7-version to 7.x-2.x.
+Fixed code style errors, as reported by pareview.sh and coder module.
+Issue #2299883: Fixed invalid WorkflowConfigTransitions created when cloning a Workflow.
+Issue #2299909: Removed 'delete'-link on Admin UI if Workflow is not deletable.
+Issue #2211145: Fixed message when only 1 Workflow Action Button avilable on form.
+Issue #2300497: Allow non-allowed transitions when $force is set.
+Issue #2296843: Added more properties and functions to entities - use them in rest of codebase.
+Issue #2211145: Added better performance when grouping workflow action buttons together with Drupal buttons ([Save], [Preview]).
+Added message on Admin pages when both WorkflowNode and WorflowField are enabled.
+
+Since 7.x-2.4
+Issue #2303925 by tucho: Fixed error due to typo in workflow_features_pipe_field_base_alter().
+Issue #2307303: Added IMPORT action to workflows, but this requires applying of patch #1967794 before it works.
+Issue #2249187: Fixed overwriting of State Name by machine name when importing a Workflow.
+Issue #2313549: Fixed workflows cannot be deleted in version 7.x-2.4
+Issue #2317243: Fixed notice: Undefined variable: new_sid in workflownode_tokens().
+Issue #2305617 by emil.virkki: Fixed Action buttons bypass Field validations.
+Issue #2326215 by sazcurrain: Fixed Views pagers with PostgreSQL DB.
+Issue #2323025: Deprecated Rules Action 'Set a Workflow Field State'.
+Issue #2314681: Fixed Notice: Trying to get property of non-object in _workflow_get_workflow_creation_sid()
+Issue #2312243: Fixed Notice: Trying to get property of non-object in _workflow_rules_workflow_get_options()
+Issue #2304365 by mgadrat: Fixed error in Rules 'Set workflow value' action when using Workflow Node.
+Issue #2323025: Reverted deprecation of Rules Action 'Set a Workflow Field State'.
+Issue #2323025: Updated Rules Action 'Set a Workflow Field State'.
+Issue #2317877: Fixed regression Fatal error: __clone method called on non-object in workflow.features.inc
+Issue #2331969 by LCM: Fixed available transitions for non-author in WorkflowState::getTransitions().
+Issue #1988990: Added translatability of Workflow Field.
+Issue #1988990: Added translatability of Workflow Field - execute scheduled transitions.
+Issue #2338547: Fixed revision_id is not set when executing a Scheduled Transitions.
+Issue #2323025: Fixed Rules Action 'Set a Workflow Field State' does not work.
+Issue #2274999: Fixing errors when creating an advanced action.
+Issue #2295625: Fixed Advanced Actions and VBO Actions.
+Issue #2072225: Added hook_help().
+Issue #2353955: Fixed caching in workflow_get_workflows_by_type().
+Issue #2354649: Reorganized submodules workflow_vbo and workflow_actions.
+Issue #2355085: Fixed Error "Workflow 0 cannot be loaded" when operating on node without Workflow.
+Issue #2355093: Fixed machine_name of '(creation)' should not be translated.
+Issue #2356605 by pianomansam: Fixed warnings when calling workflow_node_current_state()
+Issue #2371061: Fixed Notice: Undefined property: FieldCollectionItemEntity:: in WorkflowTransition->getEntity()
+Issue #2361051 by minorOffense: Fixed Call to undefined method EntityValueWrapper::getPropertyLanguage()
+Issue #2361051 by gstout: Call to undefined method EntityValueWrapper::getPropertyLanguage()
+Issue #2382377: Fatal error after disabling workflow_access: Call to undefined function workflow_access() in /entity/entity.module
+Issue #2369435: Fixed Notice: Undefined index: group in workflow_actions_trigger_info() (Line 162 of workflow_actions/workflow_actions.module)
+Issue #2260971: Updated documentation in workflow.api.php
+Issue #843568: Fixed undefined var when saving revisions.
+Issue #2304875: Fixed Status Report Tokens Warning when entity_token not enabled.
+Issue #2385439: Added Field setting to remove Workflow widget from entity edit forms.
+Issue #2228325: Moved documentation from Workflow_extensions module's README.txt to Workflow's README.txt
+Issue #2374587 by Karsa: Workflow node doesn't set the state of a newly created node
+Issue #2373103: Fixed Notice 'Undefined index' in list_field_formatter_view() when previewing a node.
+Issue #2308677: Fixed regression: 'Workflow current: Current state' is missing when creating a contextual filter
+Issue #2400211: Added Views support for non-node entity types
+
+Since 7.x-2.5
+Issue #2340011: Fixed Error "Workflow 0 cannot be loaded" when using Workflow Node and Workflow Rules
+Issue #2401481: Fixed error when creating a Transition, parameter values() is discarded
+Issue #2401493: Added $transition->getTimestamp()
+Issue #2401511: Renamed hook-functions in workflownode module
+Issue #2406175: Implemented a WorkflowTransitionForm form object
+Issue #1559680: Make Transitions fieldable entities -1-
+Issue #2406175: Uniformed creation of form_id
+Issue #2406175: Moved WorkfowTransitionEdit to th WorkflowTransitionForm form object
+Issue #2441821: Fixed wrong link
+Issue #2441821: Fixed error
+Issue #2441821: Better determination of field_name
+Issue #2421157: Added Block for viewing/changing a Workflow State
+Issue #2417193 by kaare: Added stub for undefined method WorkflowItem::delete()
+Issue #2430243: Fixed fragile check for entity edit mode in WorkflowTransitionForm::submitForm()
+Issue #2467103: State is lost when comment with Workflow Widget is submitted, and node has end-state
+Issue #2546152 by NancyDru: Added submodule workflow_notify.
+Issue #2532128 by wayaslij: EntityMetadataWrapper $property call should first check if method exists
+Issue #2413593 by kyoder: Fixed EntityMetadataWrapperException in workflow.tokens.inc
+Issue #2169859 by kyoder, BAHbKA: Problems with Workflow Access Features
+Issue #2373103 by chilic: Notice in Node Preview mode in list_field_formatter_view()
+Issue #2472501 by alan-io1: Use features_get_default in hook_features_rebuild
+Issue #2484325 by GuyPaddock: Error on Site installation: table Workflows doesn't exist
+Issue #2376557 by deetergp: Token 'comment' does not work
+Issue #2351299 by johnv: Prepare D8-Port: split class files
+Issue #2484297 by vasi, bartvig, GuyPaddock: Features import / revert broken in 2.5+
+Issue #2375261: Undo adding workflow to workflow_field features-export
+Issue #2569801 by joshf: Index {workflow_node_history}.nid
+Issue #2351299: Undo Prepare D8-Port: split class files
+Issue #2562475: Fixed Fatal PHP Error: Unsupported operand types in workflow....
+Issue #2506507: Workflow Node scheduled transition : PHP Fatal error when upd...
+Issue #2577687 by Kgaut: Fixed unclear enabling messages
+Issue #2372171: Database upgrade fails with Call to undefined function workfl...
+Issue #2587485: Fixed broken link in workflow.features.inc
+Issue #2571627: D8-port: D7-code improvements
+Issue #2600346: Enable roles for 'participate' by default on new Workflow
+Issue #2484431 by GuyPaddock: Do NOT automatically enable roles to participat...
+Issue #2502547 by djdevin, johnv: Add Entity Id to WorkflowScheduledTransition
+Issue #2600426: Fixed: Time of Scheduled Transition is not retrieved in Workf...
+Issue #2385439 by johnv: Fixed typo in code
+Issue #2514782 by johnv: Added Workflow field help text to Workflow widget
+Issue #2600808: Wrap workflow form in Fieldset
+Issue #2307303: Added dependency on entity module v7.x-1.6
+Issue #2555563 by NancyDru: Organic Groups strict access overrides Workflow A...
+Issue #2446811: Remove multiple workflows in workflow_access_node_access_reco...
+Issue #2600934 by najamfzl, johnv: Fixed PDOException: SQLSTATE[23000]: Integ...
+split form functions in own include file
+Issue #2601136: move scheduling info in own container (in WorkflowTransitionf...
+Issue #1559680: Make Transitions fieldable entities - add extra fields + fiel...
+Issue #2601240: Fixed Notice: Undefined index: id in WorkflowTransitionForm->...
+Issue #2601304: Fixed Error in Editing an existing WorkflowTransition
+Issue #2597307 by caminadaf, johnv: Fixed Workflow retrieving wrong SID on AJ...
+Issue #1511694 by joel_osc, bibishani, natew, dshields: Better integration of...
+Issue #2601304: Fixed Error in Editing an existing WorkflowTransition
+Issue #2581909: Change status via VBO
+Issue #2557905 by natanmoraes: PDOException when firing rule "Set a Workflow ...
+Issue #2601304: Error in Editing an existing WorkflowTransition
+Issue #2375261: Redo adding workflow to workflow_field features-export
+Issue #2578493: html_encode option values for workflow/state/transition
+Issue #2510958: Added css-classes to Workflow Transition Form
+Issue #2363521: Remove unnecessary action button in Node View page
+Issue #2599610 by plopesc: Schedule state change radio buttons generate confl...
+Issue #2545660: Make action buttons more unique
+Issue #2584883: [D8-port task] test all hooks
+Issue #2595247: Actions buttons: make code more local
+Issue #2600934: PDOException: SQLSTATE[23000]: Integrity constraint violation...
+Issue #2351299: Prepare D8-port: move forms in own file.
+Issue #2604914: Fatal error: Call to a member function getName() on a non-object
+Issue #2595247: Actions buttons: make code more local
+Issue #2604968: States help blocked by case switching in workflow_admin_ui_help
+Issue #2604246 by skek: Performance optimisation in Workflow::save()
+Issue #2605256 by bleedev: Unknown data property field_workflow error when de...
+Issue #2285983: Workflows Admin UI Broken on SQLite
+Issue #2581909: Change status via VBO
+Issue #2605822: Wrong default option when adding an entity with workflow
+Issue #2604048: Amend WorkflowTransition::dpm()
+Issue #2604246 by skek, danielmrichards: Performance optimisation for Workflo...
+Issue #2607150 by aronne: Workflow transitions don't respect configured permi...
+Issue #2607198 by heykarthikwithu: Add @return type and change @param values ...
+Issue #2363521: Fixed removed scheduled action button in Node View page
+Issue #2580543: Fixed warnings when displaying tokens
+Issue #318325: Add hook_widget_form_alter to workflow.api.php
+Issue #2607998: Warning: array_flip() appears when workflow_block is enabled
+Issue #2608128 by Murz: Function workflow_execute_transition() ignores timestamp of Workflow transition
+Issue #2608452 by mwidner: Fixed failing primary index in table
+Issue #2608996: Error when workflow_block on node page without workflow
+
+Since 7.x-2.6
+Issue #2612648: Implement WorkflowInterface
+Issue #2611514: Fixed Re-assigning WorkflowField when disabling state
+Issue #2612598: Fixed Warning: Missing argument n for workflow_notify_workflow()
+Issue #2613194: Show field_name in Workflow history tab
+Issue #2613204: Fixed WSOD when adding/editing a non-node entity with workflow_field

+ 0 - 0
sites/all/modules/contrib/admin/workflow/LICENSE.txt


+ 74 - 23
sites/all/modules/contrib/admin/workflow/README.txt

@@ -7,32 +7,40 @@ Maintainers: Mark Fredrickson <mark.m.fredrickson at gmail dot com>
              John VanDyk drupal.org/user/2375
              Bastlynn http://drupal.org/user/275249
              Nancy Wichmann (NancyDru) http://drupal.org/user/101412
+             John Voskuilen (johnv) http://drupal.org/user/591042
 Drupal: 7
 ********************************************************************
-DESCRIPTION:
+CONTENTS:
+ - DESCRIPTION
+ - INSTALLATION:
+ - GETTING STARTED: SETTING UP A WORKFLOW
+ - GETTING STARTED: ADDING A WORKFLOW TO A CONTENT TYPE
+ - GETTING STARTED: CREATING A NODE
+ - ADVANCED SETTINGS
+   - Token
+   - Views
+   - Avaiable hooks
+
+********************************************************************
+DESCRIPTION
 
 The workflow module enables you to create arbitrary workflows in
 Drupal and associate them with node types.
 
 Workflows are made up of workflow states.
-
 Moving from one state to another is called a transition.
-
-Actions are associated with transitions (actions.module was used
-for this in Drupal 5; core actions support is in Drupal 6).
-
-Alex Reisner introduced role-based permissions for workflow states
-and generally enhanced this module.
+Actions can be associated with transitions.
+Tokens are supported when modules Token and Entity_tokens are enabled.
 
 ********************************************************************
-INSTALLATION:
+INSTALLATION
 
 1. Place the entire workflow directory into your Drupal
    sites/all/modules directory (or appropriate alternative).
 
 2. Enable the workflow module by navigating to:
 
-     Administer > Site building > Modules
+     Administration » Modules
 
    Enabling the workflow module will create the necessary database
    tables for you.
@@ -45,17 +53,16 @@ INSTALLATION:
    to configure workflows (usually a bad idea), they must be given
    the "administer workflow" access permission:
 
-     Administer > User management > Permissions
+     Administration » People » Permissions
 
    When the module is enabled and the user has the "administer
    workflow" permission, a "Workflow" menu should appear in the
-   menu system under Administer -> Site building.
+   menu system under Configuration -> Workflow.
 
-   You may also allow only some users to schedule transitions. Select
-   the "schedule workflow transitions" permission to allow transitions.
+   You may also grant other Permissions to user roles.
 
 ********************************************************************
-GETTING STARTED:
+GETTING STARTED: SETTING UP A WORKFLOW
 
 Let's create a new workflow. Click on Administer -> Configuration ->
 Workflow -> Workflow and click on the "Add workflow" tab.
@@ -89,14 +96,22 @@ would turn in drafts and editors would say when they are "done".
 
 Be sure to click the Save button to save your settings.
 
-Now let's tell Drupal which node types should use this workflow. Click
-on Administer -> Configuration -> Workflow -> Workflow. Let's assign
-the Draft-Done workflow to the article node type and click Save Workflow
-Mapping.
-
 Now we could add an action (previously configured using the trigger
-module). Click on the Actions link above
-your workflow. Add the action to the transition.
+module). Click on the Actions link above your workflow. Add the action
+to the transition.
+
+********************************************************************
+GETTING STARTED: ADDING A WORKFLOW TO A CONTENT TYPE
+
+Now let's tell Drupal which node types should use this workflow. Go to
+
+  Administration » Structure » Content types » MY_CONTENT_TYPE
+
+Add a field of 'Field Type' Workflow. Let's assign the Draft-Done workflow
+to the article node type and click Save.
+
+********************************************************************
+GETTING STARTED: CREATING A NODE
 
 Now create a new article by going to Create content -> article. If there
 is no sign of a workflow interface here, don't panic. The interface
@@ -110,4 +125,40 @@ by clicking on the Workflow tab while viewing a node.
 Changing the state to "done" and clicking Submit will fire the action
 you set up earlier.
 
-********************************************************************
+********************************************************************
+ADVANCED SETTINGS:
+
+- In the Workflow Field Settings you'll find (amongst other settings)
+  an option to choose how to display the available target settings. You
+  may choose between 'select list', 'radio buttons' or 'action buttons'.
+  The optons show the state labels, or - if you have set them in the
+  Workflow Transitions Tab - the transition labels.
+
+- Be sure to enable the TOKEN module and the ENTITY TOKEN module to
+  make use of extra tokens. They include all properties of the latest
+  transition.
+
+  How to test the available tokens?
+  - Enable module 'Token'; use page admin/help/token;
+  - Enable module 'Token example'; use page examples/token;
+  - Enable module Automatic Entity Label, set a label, and save entity.
+
+  It also contains a token '[node:last-transition:created:seconds]'
+  that may be used with Rules to invoke actions, like sending reminder emails,
+  when content was NOT updated or a workflow did NOT transition state
+  for some time. Using these tokens you won't need PHP snippets.
+
+- If you have WORKFLOW VIEWS enabled, a "Workflow" menu item will appear in the
+  navigation menu. This displays on a single page workflow state transition
+  forms for all nodes on your system that are subject to workflow. Naturally
+  you can modify and extend this View to your heart's content.
+
+- If you want to add functionality programmatically, please check the following
+  files:
+  - workflow.api.php for all available hooks;
+  - workflow.test.inc for some rudimentary coding examples;
+
+- You'll find all settings in one of below pages:
+  - /admin/config/workflow/workflow
+  - /admin/people/permissions#module-workflow_admin_ui
+  - /admin/structure/types/manage/MY_CONTENTY_TYPE/fields/MY_FIELD

+ 2 - 2
sites/all/modules/contrib/admin/workflow/UPDATE.txt

@@ -9,7 +9,7 @@ CONTENTS OF THIS FILE
 UPDATE FROM DRUPAL 6 TO DRUPAL 7 INSTRUCTIONS
 ------------
 
-Upgrading from 6 to 7 for Workflow using this module is fairly simple. The tables and formats of Drupal 6 configurations have not been changed, nor have the names of tokens or of Views filters and plugins.
+Upgrading from 6 to 7 for Workflow using this module is fairly simple. The tables and formats of Drupal 6 configurations have not been changed, nor have the names of tokens or of Views filters and plug-ins.
 
 The only major change in the Drupal 7 update was to the Rules integration of Workflow. The following steps should lead you through the process:
 
@@ -36,4 +36,4 @@ Rules for Drupal 7 underwent major changes. Thankfully there is a built in updat
 
 4) IMPORTANT: Because the conversion is not perfect, check through each of your rules configurations for conditions and actions. You will need to change configurations for workflow states from "data selection" to "direct input" mode and re-configure the proper values to compare the given node against.
 
-Updated to Drupal 7 by Bastlynn http://drupal.org/user/275249
+Updated to Drupal 7 by Bastlynn http://drupal.org/user/275249

+ 811 - 0
sites/all/modules/contrib/admin/workflow/includes/Entity/Workflow.php

@@ -0,0 +1,811 @@
+<?php
+
+/**
+ * @file
+ * Contains workflow\includes\Entity\Workflow.
+ * Contains workflow\includes\Entity\WorkflowController.
+ */
+
+// Include file to avoid drush upgrade errors.
+include_once('WorkflowInterface.php');
+
+class Workflow extends Entity implements WorkflowInterface {
+  public $wid = 0;
+  public $name = '';
+  public $tab_roles = array();
+  public $options = array();
+  protected $creation_sid = 0;
+
+  // Attached States.
+  public $states = NULL;
+  public $transitions = NULL;
+
+  /**
+   * CRUD functions.
+   */
+
+  // public function __construct(array $values = array(), $entityType = NULL) {
+  //   return parent::__construct($values, $entityType);
+  // }
+
+  public function __clone() {
+    // Clone the arrays of States and Transitions.
+    foreach ($this->states as &$state) {
+      $state = clone $state;
+    }
+    foreach ($this->transitions as &$transition) {
+      $transition = clone $transition;
+    }
+  }
+
+  /**
+   * Given information, update or insert a new workflow.
+   *
+   * This also handles importing, rebuilding, reverting from Features,
+   * as defined in workflow.features.inc.
+   *
+   * When changing this function, test with the following situations:
+   * - maintain Workflow in Admin UI;
+   * - clone Workflow in Admin UI;
+   * - create/revert/rebuild Workflow with Features; @see workflow.features.inc
+   * - save Workflow programmatically;
+   */
+  public function save($create_creation_state = TRUE) {
+    // Are we saving a new Workflow?
+    $is_new = !empty($this->is_new);
+    // Are we rebuilding, reverting a new Workflow? @see workflow.features.inc
+    $is_rebuild = !empty($this->is_rebuild) || !empty($this->is_reverted);
+
+    if ($is_rebuild) {
+      $this->is_rebuild = TRUE;
+      $this->preRebuild();
+    }
+
+    $return = parent::save();
+
+    // On either clone or rebuild from features.
+    if ($is_new || $is_rebuild) {
+      $this->rebuildInternals();
+      if ($is_rebuild) {
+        // The above may have marked us overridden!
+        $this->status = ENTITY_IN_CODE;
+        parent::save();
+      }
+    }
+
+    // Make sure a Creation state exists.
+    if ($is_new) {
+      $state = $this->getCreationState();
+    }
+
+    workflow_reset_cache($this->wid);
+
+    return $return;
+  }
+
+  /**
+   * Rebuild things that get saved with this entity.
+   */
+  protected function preRebuild() {
+    // Remap roles. They can come from another system with shifted role IDs.
+    // See also https://drupal.org/node/1702626 .
+    $this->rebuildRoles($this->tab_roles);
+
+    // After update.php or import feature, label might be empty. @todo: remove in D8.
+    if (empty($this->label)) {
+      $this->label = $this->name;
+    }
+  }
+
+  /**
+   * Rebuild internals that get saved separately.
+   */
+  protected function rebuildInternals() {
+    // Insert the type_map when building from Features.
+    if (isset($this->typeMap)) {
+      foreach ($this->typeMap as $node_type) {
+        workflow_insert_workflow_type_map($node_type, $this->wid);
+      }
+    }
+
+    // Index the existing states and transitions by name.
+    $db_name_map = WorkflowState::getStates($this->wid, TRUE); // sid -> state.
+    $db_states = array(); // name -> state.
+    foreach ($db_name_map as $state) {
+      $db_states[$state->getName()] = $state;
+    }
+    $db_transitions = array();
+    foreach (entity_load('WorkflowConfigTransition') as $transition) {
+      if ($transition->wid == $this->wid) {
+        $start_name = $db_name_map[$transition->sid]->getName();
+        $end_name = $db_name_map[$transition->target_sid]->getName();
+        $name = WorkflowConfigTransition::machineName($start_name, $end_name);
+        $db_transitions[$name] = $transition;
+      }
+    }
+
+    // Update/create states.
+    $states = isset($this->states) ? $this->states : array();
+    $saved_states = array(); // Saved states: key -> sid.
+    $saved_state_names = array();
+    foreach ($states as $key => $data) {
+      $data = (array)$data;
+
+      $name = $data['name'];
+      if (isset($db_states[$name])) {
+        $state = $db_states[$name];
+      }
+      else {
+        $state = $this->createState($name, FALSE);
+      }
+
+      $state->wid = $this->wid;
+      $state->state = $data['state'];
+      $state->weight = $data['weight'];
+      $state->sysid = $data['sysid'];
+      if (!$data['status']) {
+        $this->rebuildStateInactive($state);
+      }
+      $state->status = $data['status'];
+      $state->save();
+
+      unset($db_states[$name]);
+      $saved_states[$key] = $state;
+      $saved_state_names[$state->sid] = $key;
+    }
+
+    // Update/create transitions.
+    $transitions = isset($this->transitions) ? $this->transitions : array();
+    foreach ($transitions as $name => $data) {
+      $data = (array)$data;
+
+      if (is_numeric($name)) {
+        $start_state = $saved_states[$saved_state_names[$data['sid']]];
+        $end_state = $saved_states[$saved_state_names[$data['target_sid']]];
+        $name = WorkflowConfigTransition::machineName($start_state->getName(),
+          $end_state->getName());
+      }
+      else {
+        $start_state = $saved_states[$data['start_state']];
+        $end_state = $saved_states[$data['end_state']];
+      }
+
+      if (isset($db_transitions[$name])) {
+        $transition = $db_transitions[$name];
+      }
+      else {
+        $transition = $this->createTransition($start_state->sid,
+          $end_state->sid);
+      }
+
+      $transition->wid = $this->wid;
+      $transition->sid = $start_state->sid;
+      $transition->target_sid = $end_state->sid;
+      $transition->label = $data['label'];
+      $transition->roles = $data['roles'];
+      $this->rebuildRoles($transition->roles);
+      $transition->save();
+
+      unset($db_transitions[$name]);
+    }
+
+    // Any states/transitions left in $db_states/transitions need deletion.
+    foreach ($db_states as $state) {
+      $this->rebuildStateInactive($state);
+      $state->delete();
+    }
+    foreach ($db_transitions as $transition) {
+      $transition->delete();
+    }
+
+    // Clear the caches, and set $this->states and $this->transitions.
+    $this->states = $this->transitions = NULL;
+    $this->getStates(TRUE, TRUE);
+    $this->getTransitions(FALSE, array(), TRUE);
+  }
+
+  /**
+   * Handle a state becoming inactive during a rebuild.
+   */
+  protected function rebuildStateInactive($state) {
+    if (!$state->isActive()) {
+      return;
+    }
+
+    // TODO: What should we do in this case? Is this safe?
+    $state->deactivate(NULL);
+  }
+
+  /**
+   * Given a wid, delete the workflow and its data.
+   *
+   * @deprecated: workflow_delete_workflows_by_wid() --> Workflow::delete().
+   */
+  public function delete() {
+    $wid = $this->wid;
+
+    // Notify any interested modules before we delete the workflow.
+    // E.g., Workflow Node deletes the {workflow_type_map} record.
+    module_invoke_all('workflow', 'workflow delete', $wid, NULL, NULL, FALSE);
+
+    // Delete associated state (also deletes any associated transitions).
+    foreach ($this->getStates($all = TRUE) as $state) {
+      $state->deactivate(0);
+      $state->delete();
+    }
+
+    // Delete the workflow.
+    db_delete('workflows')->condition('wid', $wid)->execute();
+  }
+
+  /**
+   * Validate the workflow. Generate a message if not correct.
+   *
+   * This function is used on the settings page of:
+   * - Workflow node: workflow_admin_ui_type_map_form()
+   * - Workflow field: WorkflowItem->settingsForm()
+   *
+   * @return bool
+   *   $is_valid
+   */
+  public function isValid() {
+    $is_valid = TRUE;
+
+    // Don't allow workflows with no states. There should always be a creation state.
+    $states = $this->getStates($all = FALSE);
+    if (count($states) < 1) {
+      // That's all, so let's remind them to create some states.
+      $message = t('Workflow %workflow has no states defined, so it cannot be assigned to content yet.',
+        array('%workflow' => $this->getName()));
+      drupal_set_message($message, 'warning');
+
+      // Skip allowing this workflow.
+      $is_valid = FALSE;
+    }
+
+    // Also check for transitions, at least out of the creation state. Use 'ALL' role.
+    $transitions = $this->getTransitionsBySid($this->getCreationSid(), $roles = 'ALL');
+    if (count($transitions) < 1) {
+      // That's all, so let's remind them to create some transitions.
+      $message = t('Workflow %workflow has no transitions defined, so it cannot be assigned to content yet.',
+        array('%workflow' => $this->getName()));
+      drupal_set_message($message, 'warning');
+
+      // Skip allowing this workflow.
+      $is_valid = FALSE;
+    }
+
+    // If the Workflow is mapped to a node type, check if workflow->options is set.
+    if ($this->getTypeMap() && !count($this->options)) {
+      // That's all, so let's remind them to create some transitions.
+      $message = t('Please maintain Workflow %workflow on its <a href="@url">settings</a> page.',
+        array(
+          '%workflow' => $this->getName(),
+          '@url' => url('admin/config/workflow/workflow/manage/' . $this->wid),
+        )
+      );
+      drupal_set_message($message, 'warning');
+
+      // Skip allowing this workflow.
+      // $is_valid = FALSE;
+    }
+
+    return $is_valid;
+  }
+
+  /**
+   * Returns if the Workflow may be deleted.
+   *
+   * @return bool $is_deletable
+   *   TRUE if a Workflow may safely be deleted.
+   */
+  public function isDeletable() {
+    $is_deletable = FALSE;
+
+    // May not be deleted if a TypeMap exists.
+    if ($this->getTypeMap()) {
+      return $is_deletable;
+    }
+
+    // May not be deleted if assigned to a Field.
+    foreach (_workflow_info_fields() as $field) {
+      if ($field['settings']['wid'] == $this->wid) {
+        return $is_deletable;
+      }
+    }
+
+    // May not be deleted if a State is assigned to a state.
+    foreach ($this->getStates(TRUE) as $state) {
+      if ($state->count()) {
+        return $is_deletable;
+      }
+    }
+    $is_deletable = TRUE;
+    return $is_deletable;
+  }
+
+  /**
+   * Property functions.
+   */
+
+  /**
+   * Returns the workflow id.
+   *
+   * @return int
+   *   $wid
+   */
+  public function getWorkflowId() {
+    return $this->wid;
+  }
+
+  /**
+   * Create a new state for this workflow.
+   *
+   * @param string $name
+   *   The untranslated human readable label of the state.
+   * @param bool $save
+   *   Indicator if the new state must be saved. Normally, the new State is
+   *   saved directly in the database. This is because you can use States only
+   *   with Transitions, and they rely on State IDs which are generated
+   *   magically when saving the State. But you may need a temporary state.
+   *
+   * @return WorkflowState
+   */
+  public function createState($name, $save = TRUE) {
+    $wid = $this->wid;
+    $state = workflow_state_load_by_name($name, $wid);
+    if (!$state) {
+      $state = entity_create('WorkflowState', array('name' => $name, 'state' => $name, 'wid' => $wid));
+      if ($save) {
+        $state->save();
+      }
+    }
+    $state->setWorkflow($this);
+    // Maintain the new object in the workflow.
+    $this->states[$state->sid] = $state;
+
+    return $state;
+  }
+
+  /**
+   * Gets the initial state for a newly created entity.
+   */
+  public function getCreationState() {
+    $sid = $this->getCreationSid();
+    return ($sid) ? $this->getState($sid) : $this->createState(WORKFLOW_CREATION_STATE_NAME);
+  }
+
+  /**
+   * Gets the ID of the initial state for a newly created entity.
+   */
+  public function getCreationSid() {
+    if (!$this->creation_sid) {
+      foreach ($this->getStates($all = TRUE) as $state) {
+        if ($state->isCreationState()) {
+          $this->creation_sid = $state->sid;
+        }
+      }
+    }
+    return $this->creation_sid;
+  }
+
+  /**
+   * Gets the first valid state ID, after the creation state.
+   *
+   * Uses WorkflowState::getOptions(), because this does a access check.
+   * The first State ID is user-dependent!
+   */
+  public function getFirstSid($entity_type, $entity, $field_name, $user, $force) {
+    $creation_state = $this->getCreationState();
+    $options = $creation_state->getOptions($entity_type, $entity, $field_name, $user, $force);
+    if ($options) {
+      $keys = array_keys($options);
+      $sid = $keys[0];
+    }
+    else {
+      // This should never happen, but it did during testing.
+      drupal_set_message(t('There are no workflow states available. Please notify your site administrator.'), 'error');
+      $sid = 0;
+    }
+    return $sid;
+  }
+
+  /**
+   * Returns the next state for the current state.
+   *
+   * @param string $entity_type
+   *   The type of the entity at hand.
+   * @param object $entity
+   *   The entity at hand. May be NULL (E.g., on a Field settings page).
+   * @param $field_name
+   * @param $user
+   * @param bool $force
+   *
+   * @return int $sid
+   *   A state ID.
+   */
+  public function getNextSid($entity_type, $entity, $field_name, $user, $force = FALSE) {
+    $new_sid = workflow_node_current_state($entity, $entity_type, $field_name);
+
+    if ($new_sid && $new_state = workflow_state_load_single($new_sid)) {
+      /* @var $current_state WorkflowState */
+      $options = $new_state->getOptions($entity_type, $entity, $field_name, $user, $force);
+      // Loop over every option. To find the next one.
+      $flag = $new_state->isCreationState();
+      foreach ($options as $sid => $name) {
+        if ($flag) {
+          $new_sid = $sid;
+          break;
+        }
+        if ($sid == $new_state->sid) {
+          $flag = TRUE;
+        }
+      }
+    }
+
+    return $new_sid;
+  }
+
+  /**
+   * Gets all states for a given workflow.
+   *
+   * @param mixed $all
+   *   Indicates to which states to return.
+   *   - TRUE = all, including Creation and Inactive;
+   *   - FALSE = only Active states, not Creation;
+   *   - 'CREATION' = only Active states, including Creation.
+   *
+   * @return array
+   *   An array of WorkflowState objects.
+   */
+  public function getStates($all = FALSE, $reset = FALSE) {
+    if ($this->states === NULL || $reset) {
+      $this->states = $this->wid ? WorkflowState::getStates($this->wid, $reset) : array();
+    }
+    // Do not unset, but add to array - you'll remove global objects otherwise.
+    $states = array();
+    foreach ($this->states as $state) {
+      if ($all === TRUE) {
+        $states[$state->sid] = $state;
+      }
+      elseif (($all === FALSE) && ($state->isActive() && !$state->isCreationState())) {
+        $states[$state->sid] = $state;
+      }
+      elseif (($all == 'CREATION') && ($state->isActive() || $state->isCreationState())) {
+        $states[$state->sid] = $state;
+      }
+    }
+    return $states;
+  }
+
+  /**
+   * Gets a state for a given workflow.
+   *
+   * @param mixed $key
+   *   A state ID or state Name.
+   *
+   * @return WorkflowState
+   *   A WorkflowState object.
+   */
+  public function getState($key) {
+    if (is_numeric($key)) {
+      return workflow_state_load_single($key, $this->wid);
+    }
+    else {
+      return workflow_state_load_by_name($key, $this->wid);
+    }
+  }
+
+  /**
+   * Creates a Transition for this workflow.
+   */
+  public function createTransition($sid, $target_sid, $values = array()) {
+    $workflow = $this;
+    if (is_numeric($sid) && is_numeric($target_sid)) {
+      $values['sid'] = $sid;
+      $values['target_sid'] = $target_sid;
+    }
+    else {
+      $state = $workflow->getState($sid);
+      $target_state = $workflow->getState($target_sid);
+      $values['sid'] = $state->sid;
+      $values['target_sid'] = $target_state->sid;
+    }
+
+    // First check if this transition already exists.
+    if ($transitions = entity_load('WorkflowConfigTransition', FALSE, $values)) {
+      $transition = reset($transitions);
+    }
+    else {
+      $values['wid'] = $workflow->wid;
+      $transition = entity_create('WorkflowConfigTransition', $values);
+      $transition->save();
+    }
+    $transition->setWorkflow($this);
+    // Maintain the new object in the workflow.
+    $this->transitions[$transition->tid] = $transition;
+
+    return $transition;
+  }
+
+  /**
+   * Sorts all Transitions for this workflow, according to State weight.
+   *
+   * This is only needed for the Admin UI.
+   */
+  public function sortTransitions() {
+    // Sort the transitions on state weight.
+    usort($this->transitions, '_workflow_transitions_sort_by_weight');
+  }
+
+  /**
+   * Loads all allowed ConfigTransitions for this workflow.
+   *
+   * @param mixed $tids
+   *   Array of Transitions IDs. If FALSE, show all transitions.
+   * @param array $conditions
+   *   $conditions['sid'] : if provided, a 'from' State ID.
+   *   $conditions['target_sid'] : if provided, a 'to' state ID.
+   *   $conditions['roles'] : if provided, an array of roles, or 'ALL'.
+   * @param bool $reset
+   *   Indicator to reset the cache.
+   *
+   * @return array
+   *   An array of keyed transitions.
+   */
+  public function getTransitions($tids = FALSE, array $conditions = array(), $reset = FALSE) {
+    $config_transitions = array();
+
+    // Get valid + creation states.
+    $states = $this->getStates('CREATION');
+
+    // Get filters on 'from' states, 'to' states, roles.
+    $sid = isset($conditions['sid']) ? $conditions['sid'] : FALSE;
+    $target_sid = isset($conditions['target_sid']) ? $conditions['target_sid'] : FALSE;
+    $roles = isset($conditions['roles']) ? $conditions['roles'] : 'ALL';
+
+    // Cache all transitions in the workflow.
+    // We may have 0 transitions....
+    if ($this->transitions === NULL) {
+      $this->transitions = array();
+      // Get all transitions. (Even from other workflows. :-( )
+      $config_transitions = entity_load('WorkflowConfigTransition', $tids, array(), $reset);
+      foreach ($config_transitions as &$config_transition) {
+        if (isset($states[$config_transition->sid])) {
+          $config_transition->setWorkflow($this);
+          $this->transitions[$config_transition->tid] = $config_transition;
+        }
+      }
+      $this->sortTransitions();
+    }
+
+    $config_transitions = array();
+    foreach ($this->transitions as &$config_transition) {
+      if (!isset($states[$config_transition->sid])) {
+        // Not a valid transition for this workflow.
+      }
+      elseif ($sid && $sid != $config_transition->sid) {
+        // Not the requested 'from' state.
+      }
+      elseif ($target_sid && $target_sid != $config_transition->target_sid) {
+        // Not the requested 'to' state.
+      }
+      elseif ($roles == 'ALL' || $config_transition->isAllowed($roles)) {
+        // Transition is allowed, permitted. Add to list.
+        $config_transition->setWorkflow($this);
+        $config_transitions[$config_transition->tid] = $config_transition;
+      }
+      else {
+        // Transition is otherwise not allowed.
+      }
+    }
+
+    return $config_transitions;
+  }
+
+  public function getTransitionsByTid($tid, $roles = '', $reset = FALSE) {
+    $conditions = array(
+      'roles' => $roles,
+    );
+    return $this->getTransitions(array($tid), $conditions, $reset);
+  }
+
+  public function getTransitionsBySid($sid, $roles = '', $reset = FALSE) {
+    $conditions = array(
+      'sid' => $sid,
+      'roles' => $roles,
+    );
+    return $this->getTransitions(FALSE, $conditions, $reset);
+  }
+
+  public function getTransitionsByTargetSid($target_sid, $roles = '', $reset = FALSE) {
+    $conditions = array(
+      'target_sid' => $target_sid,
+      'roles' => $roles,
+    );
+    return $this->getTransitions(FALSE, $conditions, $reset);
+  }
+
+  /**
+   * Get a specific transition. Therefore, use $roles = 'ALL'.
+   */
+  public function getTransitionsBySidTargetSid($sid, $target_sid, $roles = 'ALL', $reset = FALSE) {
+    $conditions = array(
+      'sid' => $sid,
+      'target_sid' => $target_sid,
+      'roles' => $roles,
+    );
+    return $this->getTransitions(FALSE, $conditions, $reset);
+  }
+
+  /**
+   * Gets a the type map for a given workflow.
+   *
+   * @param int $sid
+   *   A state ID.
+   *
+   * @return array
+   *   An array of typemaps.
+   */
+  public function getTypeMap() {
+    $result = array();
+
+    $type_maps = module_exists('workflownode') ? workflow_get_workflow_type_map_by_wid($this->wid) : array();
+    foreach ($type_maps as $map) {
+      $result[] = $map->type;
+    }
+
+    return $result;
+  }
+
+  /**
+   * Gets a setting from the state object.
+   */
+  public function getSetting($key, array $field = array()) {
+    switch ($key) {
+      case 'watchdog_log':
+        if (isset($this->options['watchdog_log'])) {
+          // This is set via Node API.
+          return $this->options['watchdog_log'];
+        }
+        elseif ($field) {
+          if (isset($field['settings']['watchdog_log'])) {
+            // This is set via Field API.
+            return $field['settings']['watchdog_log'];
+          }
+        }
+        drupal_set_message('Setting Workflow::getSetting(' . $key . ') does not exist', 'error');
+        break;
+
+      default:
+        drupal_set_message('Setting Workflow::getSetting(' . $key . ') does not exist', 'error');
+    }
+  }
+
+  /**
+   * Mimics Entity API functions.
+   */
+  public function getName() {
+    return $this->name;
+  }
+
+  protected function defaultLabel() {
+    return isset($this->label) ? $this->label : '';
+  }
+
+  protected function defaultUri() {
+    return array('path' => 'admin/config/workflow/workflow/manage/' . $this->wid);
+  }
+
+  protected function rebuildRoles(array &$roles) {
+    $role_map = isset($this->system_roles) ? $this->system_roles : array();
+    if (!$role_map) {
+      return;
+    }
+
+    // See also https://drupal.org/node/1702626 .
+    $new_roles = array();
+    foreach ($roles as $key => $rid) {
+      if ($rid == WORKFLOW_ROLE_AUTHOR_RID) {
+        $new_roles[$rid] = $rid;
+      }
+      else {
+        if ($role = user_role_load_by_name($role_map[$rid])) {
+          $new_roles[$role->rid] = (int)($role->rid);
+        }
+      }
+    }
+    $roles = $new_roles;
+  }
+
+}
+
+/**
+ * Helper function to sort the transitions.
+ *
+ * @param WorkflowConfigTransition $a
+ * @param WorkflowConfigTransition $b
+ *
+ * @return int
+ */
+function _workflow_transitions_sort_by_weight($a, $b) {
+  // First sort on From-State.
+  $old_state_a = $a->getOldState();
+  $old_state_b = $b->getOldState();
+  if ($old_state_a->weight < $old_state_b->weight) return -1;
+  if ($old_state_a->weight > $old_state_b->weight) return +1;
+
+  // Then sort on To-State.
+  $new_state_a = $a->getNewState();
+  $new_state_b = $b->getNewState();
+  if ($new_state_a->weight < $new_state_b->weight) return -1;
+  if ($new_state_a->weight > $new_state_b->weight) return +1;
+  return 0;
+}
+
+
+/**
+ * Implements a controller class for Workflow.
+ */
+class WorkflowController extends EntityAPIControllerExportable {
+
+  // public function create(array $values = array()) {    return parent::create($values);  }
+  // public function load($ids = array(), $conditions = array()) { }
+
+  public function delete($ids, DatabaseTransaction $transaction = NULL) {
+    // @todo: replace WorkflowController::delete() with parent.
+    // @todo: throw error if not workflow->isDeletable().
+    foreach ($ids as $wid) {
+      if ($workflow = workflow_load($wid)) {
+        $workflow->delete();
+      }
+    }
+    $this->resetCache();
+  }
+
+  /**
+   * Overrides DrupalDefaultEntityController::cacheGet().
+   *
+   * Override default function, due to Core issue #1572466.
+   */
+  protected function cacheGet($ids, $conditions = array()) {
+    // Load any available entities from the internal cache.
+    if ($ids === FALSE && !$conditions) {
+      return $this->entityCache;
+    }
+    return parent::cacheGet($ids, $conditions);
+  }
+
+  /**
+   * Overrides DrupalDefaultEntityController::cacheSet().
+   */
+  /*
+    // protected function cacheSet($entities) { }
+    //   return parent::cacheSet($entities);
+    // }
+   */
+
+  /**
+   * Overrides DrupalDefaultEntityController::resetCache().
+   *
+   * Called by workflow_reset_cache, to
+   * Reset the Workflow when States, Transitions have been changed.
+   */
+  // public function resetCache(array $ids = NULL) {
+  //   parent::resetCache($ids);
+  // }
+
+  /**
+   * Overrides DrupalDefaultEntityController::attachLoad().
+   */
+  protected function attachLoad(&$queried_entities, $revision_id = FALSE) {
+    foreach ($queried_entities as $entity) {
+      // Load the states, so they are already present on the next (cached) load.
+      $entity->states = $entity->getStates($all = TRUE);
+      $entity->transitions = $entity->getTransitions(FALSE);
+      $entity->typeMap = $entity->getTypeMap();
+    }
+
+    parent::attachLoad($queried_entities, $revision_id);
+  }
+}

+ 192 - 0
sites/all/modules/contrib/admin/workflow/includes/Entity/WorkflowConfigTransition.php

@@ -0,0 +1,192 @@
+<?php
+
+/**
+ * @file
+ * Contains workflow\includes\Entity\WorkflowConfigTransition.
+ * Contains workflow\includes\Entity\WorkflowConfigTransitionController.
+ */
+
+/**
+ * Implements a configurated Transition.
+ */
+class WorkflowConfigTransition extends Entity {
+
+  // Transition data.
+  public $tid = 0;
+  // public $old_sid = 0;
+  // public $new_sid = 0;
+  public $sid = 0; // @todo D8: remove $sid, use $new_sid. (requires conversion of Views displays.)
+  public $target_sid = 0;
+  public $roles = array();
+
+  // Extra fields.
+  public $wid = 0;
+  // The following must explicitely defined, and not be public, to avoid errors when exporting with json_encode().
+  protected $workflow = NULL;
+
+  /**
+   * Entity class functions.
+   */
+
+/*
+  // Implementing clone needs a list of tid-less transitions, and a conversion
+  // of sids for both States and ConfigTransitions.
+  // public function __clone() {}
+ */
+
+  public function __construct(array $values = array(), $entityType = NULL) {
+    // Please be aware that $entity_type and $entityType are different things!
+    return parent::__construct($values, $entityType = 'WorkflowConfigTransition');
+  }
+
+  /**
+   * Permanently deletes the entity.
+   */
+  public function delete() {
+    // Notify any interested modules before we delete, in case there's data needed.
+    // @todo D8: this can be replaced by a hook_entity_delete(?)
+    module_invoke_all('workflow', 'transition delete', $this->tid, NULL, NULL, FALSE);
+
+    return parent::delete();
+  }
+
+  protected function defaultLabel() {
+    return $this->label;
+  }
+
+  protected function defaultUri() {
+    return array('path' => 'admin/config/workflow/workflow/manage/' . $this->wid . '/transitions/');
+  }
+
+  /**
+   * Property functions.
+   */
+
+  /**
+   * Returns the Workflow object of this State.
+   *
+   * @param Workflow $workflow
+   *   An optional workflow object. Can be used as a setter.
+   *
+   * @return Workflow
+   *   Workflow object.
+   */
+  public function setWorkflow($workflow) {
+    $this->wid = $workflow->wid;
+    $this->workflow = $workflow;
+  }
+
+  public function getWorkflow() {
+    if (isset($this->workflow)) {
+      return $this->workflow;
+    }
+    return workflow_load_single($this->wid);
+  }
+  public function getOldState() {
+    return workflow_state_load_single($this->sid);
+  }
+  public function getNewState() {
+    return workflow_state_load_single($this->target_sid);
+  }
+
+  /**
+   * Verifies if the given transition is allowed.
+   *
+   * - In settings;
+   * - In permissions;
+   * - By permission hooks, implemented by other modules.
+   *
+   * @return bool
+   *   TRUE if OK, else FALSE.
+   */
+  public function isAllowed($user_roles) {
+    if ($user_roles == 'ALL') {
+      // Superuser.
+      return TRUE;
+    }
+    elseif ($user_roles) {
+      return array_intersect($user_roles, $this->roles) == TRUE;
+    }
+    return TRUE;
+  }
+
+  /**
+   * Generate a machine name for a transition.
+   */
+  public static function machineName($start_name, $end_name) {
+    $new_name   = sprintf("%s_to_%s", $start_name, $end_name);
+
+    // Special case: replace parens in creation state transition names.
+    $new_name   = str_replace("(creation)", "_creation", $new_name);
+
+    return $new_name;
+  }
+
+  public function save() {
+    parent::save();
+
+    // Ensure Workflow is marked overridden.
+    $workflow = $this->getWorkflow();
+    if ($workflow->status == ENTITY_IN_CODE) {
+      $workflow->status = ENTITY_OVERRIDDEN;
+      $workflow->save();
+    }
+  }
+}
+
+/**
+ * Implements a controller class for WorkflowConfigTransition.
+ *
+ * The 'true' controller class is 'Workflow'.
+ */
+class WorkflowConfigTransitionController extends EntityAPIController {
+
+  /**
+   * Overrides DrupalDefaultEntityController::cacheGet().
+   *
+   * Override default function, due to core issue #1572466.
+   */
+  protected function cacheGet($ids, $conditions = array()) {
+    // Load any available entities from the internal cache.
+    if ($ids === FALSE && !$conditions) {
+      return $this->entityCache;
+    }
+    return parent::cacheGet($ids, $conditions);
+  }
+
+  public function save($entity, DatabaseTransaction $transaction = NULL) {
+    $workflow = $entity->getWorkflow();
+
+    // To avoid double posting, check if this transition already exist.
+    if (empty($entity->tid)) {
+      if ($workflow) {
+        $config_transitions = $workflow->getTransitionsBySidTargetSid($entity->sid, $entity->target_sid);
+        $config_transition = reset($config_transitions);
+        if ($config_transition) {
+          $entity->tid = $config_transition->tid;
+        }
+      }
+    }
+
+    // Create the machine_name. This can be used to rebuild/revert the Feature in a target system.
+    if (empty($entity->name)) {
+      $entity->name = $entity->sid . '_' . $entity->target_sid;
+    }
+
+    $return = parent::save($entity, $transaction);
+    if ($return) {
+      // Save in current workflow for the remainder of this page request.
+      // Keep in sync with Workflow::getTransitions() !
+      $workflow = $entity->getWorkflow();
+      if ($workflow) {
+        $workflow->transitions[$entity->tid] = $entity;
+        // $workflow->sortTransitions();
+      }
+    }
+
+    // Reset the cache for the affected workflow, to force reload upon next page_load.
+    workflow_reset_cache($entity->wid);
+
+    return $return;
+  }
+}

+ 180 - 0
sites/all/modules/contrib/admin/workflow/includes/Entity/WorkflowInterface.php

@@ -0,0 +1,180 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\workflow\Entity\WorkflowInterface.
+ */
+
+// D8: namespace Drupal\workflow\Entity;
+
+// D8: use Drupal\Core\Config\Entity\ConfigEntityBase;
+// D8: use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Defines a common interface for Workflow*Transition* objects.
+ *
+ * @see \Drupal\workflow\Entity\WorkflowConfigTransition
+ * @see \Drupal\workflow\Entity\WorkflowTransition
+ * @see \Drupal\workflow\Entity\WorkflowScheduledTransition
+ */
+interface WorkflowInterface {
+
+  /**
+   * Retrieves the entity manager service.
+   *
+   * @return \Drupal\workflow\Entity\WorkflowManagerInterface
+   *   The entity manager service.
+   */
+//D8: public static function workflowManager();
+
+  /**
+   * Returns the workflow id.
+   *
+   * @return string
+   *   $wid
+   */
+  public function getWorkflowId();
+
+  /**
+   * Validate the workflow. Generate a message if not correct.
+   *
+   * This function is used on the settings page of:
+   * - Workflow field: WorkflowItem->settingsForm()
+   *
+   * @return bool
+   *   $is_valid
+   */
+  public function isValid();
+
+  /**
+   * Returns if the Workflow may be deleted.
+   *
+   * @return bool $is_deletable
+   *   TRUE if a Workflow may safely be deleted.
+   */
+  public function isDeletable();
+
+  /**
+   * Create a new state for this workflow.
+   *
+   * @param string $name
+   *   The untranslated human readable label of the state.
+   * @param bool $save
+   *   Indicator if the new state must be saved. Normally, the new State is
+   *   saved directly in the database. This is because you can use States only
+   *   with Transitions, and they rely on State IDs which are generated
+   *   magically when saving the State. But you may need a temporary state.
+   * @return \Drupal\workflow\Entity\WorkflowState
+   *   The new state.
+   */
+  public function createState($sid, $save = TRUE);
+
+  /**
+   * Gets the initial state for a newly created entity.
+   */
+  public function getCreationState();
+
+  /**
+   * Gets the ID of the initial state for a newly created entity.
+   */
+  public function getCreationSid();
+
+  /**
+   * Gets the first valid state ID, after the creation state.
+   *
+   * Uses WorkflowState::getOptions(), because this does an access check.
+   * The first State ID is user-dependent!
+   */
+  public function getFirstSid($entity_type, $entity, $field_name, $user, $force);
+
+  /**
+   * Returns the next state for the current state.
+   * Is used in VBO Bulk actions.
+   *
+   * @param string $entity_type
+   *   The type of the entity at hand.
+   * @param object $entity
+   *   The entity at hand. May be NULL (E.g., on a Field settings page).
+   * @param $field_name
+   * @param $user
+   * @param bool $force
+   *
+   * @return array
+   *   An array of sid=>label pairs.
+   *   If $this->sid is set, returns the allowed transitions from this state.
+   *   If $this->sid is 0 or FALSE, then labels of ALL states of the State's
+   *   Workflow are returned.
+   *
+   */
+  public function getNextSid($entity_type, $entity, $field_name, $user, $force = FALSE);
+
+  /**
+   * Gets all states for a given workflow.
+   *
+   * @param mixed $all
+   *   Indicates to which states to return.
+   *   - TRUE = all, including Creation and Inactive;
+   *   - FALSE = only Active states, not Creation;
+   *   - 'CREATION' = only Active states, including Creation.
+   *
+   * @return WorkflowState[]
+   *   An array of WorkflowState objects.
+   */
+  public function getStates($all = FALSE, $reset = FALSE);
+
+  /**
+   * Gets a state for a given workflow.
+   *
+   * @param mixed $key
+   *   A state ID or state Name.
+   *
+   * @return WorkflowState
+   *   A WorkflowState object.
+   */
+  public function getState($sid);
+
+  /**
+   * Creates a Transition for this workflow.
+   *
+   * @param string $from_sid
+   * @param string $to_sid
+   * @param array $values
+   *
+   * @return mixed|null|static
+   */
+  public function createTransition($from_sid, $to_sid, $values = array());
+
+  /**
+   * Sorts all Transitions for this workflow, according to State weight.
+   *
+   * This is only needed for the Admin UI.
+   */
+  public function sortTransitions();
+
+  /**
+   * Loads all allowed ConfigTransitions for this workflow.
+   *
+   * @param array|NULL $ids
+   *   Array of Transitions IDs. If NULL, show all transitions.
+   * @param array $conditions
+   *   $conditions['from_sid'] : if provided, a 'from' State ID.
+   *   $conditions['to_sid'] : if provided, a 'to' state ID.
+   *
+   * @return \Drupal\workflow\Entity\WorkflowConfigTransition[]
+   */
+  public function getTransitions($tids = FALSE, array $conditions = array(), $reset = FALSE);
+
+  public function getTransitionsByTid($tid);
+
+  /**
+   *
+   * Get a specific transition.
+   *
+   * @param string $from_sid
+   * @param string $to_sid
+   *
+   * @return WorkflowConfigTransition[]
+   */
+//D8: public function getTransitionsByStateId($from_sid, $to_sid);
+
+}

+ 165 - 0
sites/all/modules/contrib/admin/workflow/includes/Entity/WorkflowScheduledTransition.php

@@ -0,0 +1,165 @@
+<?php
+
+/**
+ * @file
+ * Contains workflow\includes\Entity\WorkflowScheduledTransition.
+ */
+
+/**
+ * Implements a scheduled transition, as shown on Workflow form.
+ */
+class WorkflowScheduledTransition extends WorkflowTransition {
+  // Scheduled timestamp of state change.
+  public $scheduled;
+
+  /**
+   * Constructor.
+   */
+  public function __construct(array $values = array(), $entityType = 'WorkflowScheduledTransition') {
+    // Please be aware that $entity_type and $entityType are different things!
+    parent::__construct($values, $entityType);
+
+    $this->is_scheduled = TRUE;
+    $this->is_executed = FALSE;
+  }
+
+  public function setValues($entity_type, $entity, $field_name, $old_sid, $new_sid, $uid = NULL, $scheduled = REQUEST_TIME, $comment = '') {
+    // A scheduled transition does not have a timestamp, yet.
+    $stamp = 0;
+    parent::setValues($entity_type, $entity, $field_name, $old_sid, $new_sid, $uid, $stamp, $comment);
+
+    // Set the scheduled timestamp of state change.
+    $this->scheduled = $scheduled;
+  }
+
+  /**
+   * Given a node, get all scheduled transitions for it.
+   *
+   * @param string $entity_type
+   * @param int $entity_id
+   * @param string $field_name
+   *   Optional.
+   *
+   * @return array
+   *   An array of WorkflowScheduledTransitions.
+   *
+   * deprecated: workflow_get_workflow_scheduled_transition_by_nid() --> WorkflowScheduledTransition::load()
+   */
+  public static function load($entity_type, $entity_id, $field_name = '', $limit = NULL) {
+    if (!$entity_id) {
+      return array();
+    }
+
+    $query = db_select('workflow_scheduled_transition', 'wst');
+    $query->fields('wst');
+    $query->condition('entity_type', $entity_type, '=');
+    $query->condition('nid', $entity_id, '=');
+    if ($field_name !== NULL) {
+      $query->condition('field_name', $field_name, '=');
+    }
+    $query->orderBy('scheduled', 'ASC');
+    $query->addTag('workflow_scheduled_transition');
+    if ($limit) {
+      $query->range(0, $limit);
+    }
+    $result = $query->execute()->fetchAll(PDO::FETCH_CLASS, 'WorkflowScheduledTransition');
+
+    return $result;
+  }
+
+  /**
+   * Given a timeframe, get all scheduled transitions.
+   *
+   * deprecated: workflow_get_workflow_scheduled_transition_by_between() --> WorkflowScheduledTransition::loadBetween()
+   */
+  public static function loadBetween($start = 0, $end = 0) {
+    $query = db_select('workflow_scheduled_transition', 'wst');
+    $query->fields('wst');
+    $query->orderBy('scheduled', 'ASC');
+    $query->addTag('workflow_scheduled_transition');
+
+    if ($start) {
+      $query->condition('scheduled', $start, '>');
+    }
+    if ($end) {
+      $query->condition('scheduled', $end, '<');
+    }
+
+    $result = $query->execute()->fetchAll(PDO::FETCH_CLASS, 'WorkflowScheduledTransition');
+    return $result;
+  }
+
+  /**
+   * Save a scheduled transition. If the transition is executed, save in history.
+   */
+  public function save() {
+    // If executed, save in history.
+    if ($this->is_executed) {
+      // Be careful, we are not a WorkflowScheduleTransition anymore!
+      $this->entityType = 'WorkflowTransition';
+      $this->setUp();
+
+      return parent::save(); // <--- exit !!
+    }
+
+    // Since we do not have an entity_id here, we cannot use entity_delete.
+    // @todo: Add an 'entity id' to WorkflowScheduledTransition entity class.
+    // $result = parent::save();
+
+    // Avoid duplicate entries.
+    $clone = clone $this;
+    $clone->delete();
+    // Save (insert or update) a record to the database based upon the schema.
+    drupal_write_record('workflow_scheduled_transition', $this);
+
+    // Create user message.
+    if ($state = $this->getNewState()) {
+      $entity_type = $this->entity_type;
+      $entity = $this->getEntity();
+      $message = '%entity_title scheduled for state change to %state_name on %scheduled_date';
+      $args = array(
+        '@entity_type' => $entity_type,
+        '%entity_title' => entity_label($entity_type, $entity),
+        '%state_name' => entity_label('WorkflowState', $state),
+        '%scheduled_date' => format_date($this->scheduled),
+      );
+      $uri = entity_uri($entity_type, $entity);
+      watchdog('workflow', $message, $args, WATCHDOG_NOTICE, l('view', $uri['path'] . '/workflow'));
+      drupal_set_message(t($message, $args));
+    }
+  }
+
+  /**
+   * Given a node, delete transitions for it.
+   *
+   * deprecated: workflow_delete_workflow_scheduled_transition_by_nid() --> WorkflowScheduledTransition::delete()
+   */
+  public function delete() {
+    // Support translated Workflow Field workflows by including the language.
+    db_delete($this->entityInfo['base table'])
+        ->condition('entity_type', $this->entity_type)
+        ->condition('nid', $this->entity_id)
+        ->condition('field_name', $this->field_name)
+        ->condition('language', $this->language)
+        ->execute();
+  }
+
+  /**
+   * Property functions.
+   */
+
+  /**
+   * If a scheduled transition has no comment, a default comment is added before executing it.
+   */
+  public function addDefaultComment() {
+    $this->comment = t('Scheduled by user @uid.', array('@uid' => $this->uid));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTimestamp() {
+    return $this->scheduled;
+  }
+
+}

+ 607 - 0
sites/all/modules/contrib/admin/workflow/includes/Entity/WorkflowState.php

@@ -0,0 +1,607 @@
+<?php
+
+/**
+ * @file
+ * Contains workflow\includes\Entity\WorkflowState.
+ * Contains workflow\includes\Entity\WorkflowStateController.
+ */
+
+/**
+ * Class WorkflowState
+ */
+class WorkflowState extends Entity {
+  // Since workflows do not change, it is implemented as a singleton.
+  protected static $states = array();
+
+  public $sid = 0;
+  public $wid = 0;
+  public $weight = 0;
+  public $sysid = 0;
+  public $state = ''; // @todo D8: remove $state, use $label/$name. (requires conversion of Views displays.)
+  public $status = 1;
+
+  /**
+   * CRUD functions.
+   */
+
+  /**
+   * Constructor.
+   *
+   * @param array $values
+   * @param string $entityType
+   */
+  public function __construct(array $values = array(), $entityType = 'WorkflowState') {
+    // Please be aware that $entity_type and $entityType are different things!
+
+    // Keep official name and external name equal. Both are required.
+    // @todo: still needed? test import, manual creation, programmatic creation, etc.
+    if (!isset($values['state']) && isset($values['name'])) {
+      $values['state'] = $values['name'];
+    }
+
+    // Set default values for '(creation)' state.
+    if (!empty($values['is_new']) && $values['name'] == WORKFLOW_CREATION_STATE_NAME) {
+      $values['sysid'] = WORKFLOW_CREATION;
+      $values['weight'] = WORKFLOW_CREATION_DEFAULT_WEIGHT;
+      $values['name'] = '(creation)'; // machine_name;
+    }
+    parent::__construct($values, $entityType);
+
+    if (empty($values)) {
+      // Automatic constructor when casting an array or object.
+      // Add pre-existing states to cache (not new/temp ones).
+      if (!isset(self::$states[$this->sid])) {
+        self::$states[$this->sid] = $this;
+      }
+    }
+  }
+
+/*
+  // Implementing clone needs a list of tid-less transitions, and a conversion
+  // of sids for both States and ConfigTransitions.
+  // public function __clone() {}
+ */
+
+  /**
+   * Alternative constructor, loading objects from table {workflow_states}.
+   *
+   * @param int $sid
+   *   The requested State ID
+   * @param int $wid
+   *   An optional Workflow ID, to check if the requested State is valid for the Workflow.
+   *
+   * @return WorkflowState|NULL|FALSE $state
+   *   WorkflowState if state is successfully loaded,
+   *   NULL if not loaded,
+   *   FALSE if state does not belong to requested Workflow.
+   */
+  public static function load($sid, $wid = 0) {
+    $states = self::getStates();
+    $state = isset($states[$sid]) ? $states[$sid] : NULL;
+    if ($wid && $state && ($wid != $state->wid)) {
+      return FALSE;
+    }
+    return $state;
+  }
+
+  /**
+   * Get all states in the system, with options to filter, only where a workflow exists.
+   *
+   * @param $wid
+   *   The requested Workflow ID.
+   * @param bool $reset
+   *   An option to refresh all caches.
+   *
+   * @return array $states
+   *   An array of cached states.
+   *
+   * D7.x-2.x: deprecated workflow_get_workflow_states --> workflow_state_load_multiple
+   * D7.x-2.x: deprecated workflow_get_workflow_states_all --> workflow_state_load_multiple
+   * D7.x-2.x: deprecated workflow_get_other_states_by_sid --> workflow_state_load_multiple
+   */
+  public static function getStates($wid = 0, $reset = FALSE) {
+    if ($reset) {
+      self::$states = array();
+    }
+
+    if (empty(self::$states)) {
+      // Build the query, and get ALL states.
+      // Note: self::states[] is populated in respective constructors.
+      $query = db_select('workflow_states', 'ws');
+      $query->fields('ws');
+      $query->orderBy('ws.weight');
+      $query->orderBy('ws.wid');
+      // Just for grins, add a tag that might result in modifications.
+      $query->addTag('workflow_states');
+
+      // @see #2285983 for using SQLite.
+      // $query->execute()->fetchAll(PDO::FETCH_CLASS, 'WorkflowState');
+      /* @var $tmp DatabaseStatementBase */
+      $statement = $query->execute();
+      $statement->setFetchMode(PDO::FETCH_CLASS,'WorkflowState');
+      foreach ($statement->fetchAll() as $state) {
+        self::$states[$state->sid] = $state;
+      }
+    }
+
+    if (!$wid) {
+      // All states are requested and cached: return them.
+      return self::$states;
+    }
+    else {
+      // All states of only 1 Workflow is requested: return this one.
+      $result = array();
+      foreach (self::$states as $state) {
+        if ($state->wid == $wid) {
+          $result[$state->sid] = $state;
+        }
+      }
+      return $result;
+    }
+  }
+
+  /**
+   * Get all states in the system, with options to filter, only where a workflow exists.
+   *
+   * May return more then one State, since a name is not (yet) an UUID.
+   *
+   * @param $name
+   * @param int $wid
+   *
+   * @return WorkflowState
+   */
+  public static function loadByName($name, $wid = 0) {
+    /* @var $state WorkflowState */
+    foreach ($states = self::getStates($wid) as $state) {
+      if ($name == $state->getName()) {
+        return $state;
+      }
+    }
+    return NULL;
+  }
+
+  /**
+   * Deactivate a Workflow State, moving existing nodes to a given State.
+   *
+   * @param int $new_sid
+   *   The state ID, to which all affected entities must be moved.
+   *
+   * D7.x-2.x: deprecated workflow_delete_workflow_states_by_sid() --> WorkflowState->deactivate() + delete()
+   */
+  public function deactivate($new_sid) {
+    $current_sid = $this->sid;
+    $force = TRUE;
+
+    // Notify interested modules. We notify first to allow access to data before we zap it.
+    // E.g., Node API implements this.
+    // - re-parents any nodes that we don't want to orphan, whilst deactivating a State.
+    // - delete any lingering node to state values.
+    module_invoke_all('workflow', 'state delete', $current_sid, $new_sid, NULL, $force);
+
+    // Re-parent any nodes that we don't want to orphan, whilst deactivating a State.
+    if ($new_sid) {
+      // A candidate for the batch API.
+      // @TODO: Future updates should seriously consider setting this with batch.
+
+      global $user; // We can use global, since deactivate() is a UI-only function.
+      $comment = t('Previous state deleted');
+
+      // Re-assign workflow_node nodes.
+      foreach (workflow_get_workflow_node_by_sid($current_sid) as $workflow_node) {
+        // @todo: add Field support in 'state delete', by using workflow_node_history or reading current field.
+        $entity_type = 'node';
+        $entity = entity_load_single('node', $workflow_node->nid);
+        $field_name = '';
+        $transition = new WorkflowTransition();
+        $transition->setValues($entity_type, $entity, $field_name, $current_sid, $new_sid, $user->uid, REQUEST_TIME, $comment);
+        $transition->force($force);
+        // Execute Transition, invoke 'pre' and 'post' events, save new state in workflow_node, save also in workflow_node_history.
+        // For Workflow Node, only {workflow_node} and {workflow_node_history} are updated. For Field, also the Entity itself.
+        $new_sid = workflow_execute_transition($entity_type, $entity, $field_name, $transition, $force);
+      }
+      // Re-assign workflow_field_entities.
+      foreach(_workflow_info_fields() as $field_name => $field_info) {
+        $query = new EntityFieldQuery();
+        $query->fieldCondition($field_name, 'value', $current_sid, '=');
+        $result = $query->execute();
+        foreach ($result as $entity_type => $entities) {
+          if ($entity_type == 'comment') {
+            // Do not reset comments.
+            continue;
+          }
+          foreach ($entities as $entity_id => $entity) {
+            $entity = entity_load_single($entity_type, $entity_id);
+            /* @var $transition WorkflowTransition */
+            $transition = new WorkflowTransition();
+            $transition->setValues($entity_type, $entity, $field_name, $current_sid, $new_sid, $user->uid, REQUEST_TIME, $comment, TRUE);
+            $transition->force($force);
+
+            // Execute Transition, invoke 'pre' and 'post' events, save new state in Field-table, save also in workflow_transition_history.
+            // For Workflow Node, only {workflow_node} and {workflow_transition_history} are updated. For Field, also the Entity itself.
+            $new_sid = workflow_execute_transition($entity_type, $entity, $field_name, $transition, $force);
+          }
+        }
+      }
+
+    }
+    // Delete any lingering node to state values.
+    workflow_delete_workflow_node_by_sid($current_sid);
+
+    // Delete the config transitions this state is involved in.
+    $workflow = workflow_load_single($this->wid);
+    /* @var $transition WorkflowTransition */
+    foreach ($workflow->getTransitionsBySid($current_sid, 'ALL') as $transition) {
+      $transition->delete();
+    }
+    foreach ($workflow->getTransitionsByTargetSid($current_sid, 'ALL') as $transition) {
+      $transition->delete();
+    }
+
+    // Delete the state. -- We don't actually delete, just deactivate.
+    // This is a matter up for some debate, to delete or not to delete, since this
+    // causes name conflicts for states. In the meantime, we just stick with what we know.
+    // If you really want to delete the states, use workflow_cleanup module, or delete().
+    $this->status = FALSE;
+    $this->save();
+
+    // Clear the cache.
+    self::getStates(0, TRUE);
+  }
+
+  /**
+   * Property functions.
+   */
+
+  /**
+   * Returns the Workflow object of this State.
+   *
+   * @return Workflow
+   *   Workflow object.
+   */
+  public function getWorkflow() {
+    if (isset($this->workflow)) {
+      return $this->workflow;
+    }
+    return workflow_load_single($this->wid);
+  }
+
+  public function setWorkflow($workflow) {
+    $this->wid = $workflow->wid;
+    $this->workflow = $workflow;
+  }
+
+  /**
+   * Returns the Workflow object of this State.
+   *
+   * @return bool
+   *   TRUE if state is active, else FALSE.
+   */
+  public function isActive() {
+    return (bool) $this->status;
+  }
+
+  public function isCreationState() {
+    return $this->sysid == WORKFLOW_CREATION;
+  }
+
+  /**
+   * Determines if the Workflow Form must be shown.
+   *
+   * If not, a formatter must be shown, since there are no valid options.
+   *
+   * @param $entity_type
+   * @param $entity
+   * @param $field_name
+   * @param $user
+   * @param $force
+   *
+   * @return bool $show_widget
+   *   TRUE = a form (a.k.a. widget) must be shown; FALSE = no form, a formatter must be shown instead.
+   */
+  public function showWidget($entity_type, $entity, $field_name, $user, $force) {
+    $options = $this->getOptions($entity_type, $entity, $field_name, $user, $force);
+    $count = count($options);
+    // The easiest case first: more then one option: always show form.
+    if ($count > 1) {
+      return TRUE;
+    }
+    // #2226451: Even in Creation state, we must have 2 visible states to show the widget.
+    // // Only when in creation phase, one option is sufficient,
+    // // since the '(creation)' option is not included in $options.
+    // // When in creation state,
+    // if ($this->isCreationState()) {
+    // return TRUE;
+    // }
+    return FALSE;
+  }
+
+  /**
+   * Returns the allowed transitions for the current state.
+   *
+   * @param string $entity_type
+   *   The type of the entity at hand.
+   * @param object $entity
+   *   The entity at hand. May be NULL (E.g., on a Field settings page).
+   * @param string $field_name
+   * @param null $user
+   * @param bool $force
+   *
+   * @return array
+   * An array of tid=>transition pairs with allowed transitions for State.
+   */
+  public function getTransitions($entity_type = '', $entity = NULL, $field_name = '', $user = NULL, $force = FALSE) {
+    $transitions = array();
+
+    $current_sid = $this->sid;
+    $current_state = $this;
+
+    if (!$workflow = $this->getWorkflow()) {
+      // No workflow, no options ;-)
+      return $transitions;
+    }
+
+    // Get the role IDs of the user, to get the proper permissions.
+    $roles = $user ? array_keys($user->roles) : array();
+
+    // Some entities (e.g., taxonomy_term) do not have a uid.
+    $entity_uid = isset($entity->uid) ? $entity->uid : 0;
+
+    // Fetch entity_id from entity for _newness_ check
+    $entity_id = ($entity) ? entity_id($entity_type, $entity) : '';
+
+    if ($force || ($user && $user->uid == 1)) {
+      // Superuser is special. And $force allows Rules to cause transition.
+      $roles = 'ALL';
+    }
+    elseif ($entity && (!empty($entity->is_new) || empty($entity_id))) {
+      // Add 'author' role to user, if this is a new entity.
+      // - $entity can be NULL (E.g., on a Field settings page).
+      // - on display of new entity, $entity_id and $is_new are not set.
+      // - on submit of new entity, $entity_id and $is_new are both set.
+      $roles = array_merge(array(WORKFLOW_ROLE_AUTHOR_RID), $roles);
+    }
+    elseif (($entity_uid > 0) && ($user->uid > 0) && ($entity_uid == $user->uid)) {
+      // Add 'author' role to user, if user is author of this entity.
+      // - Some entities (e.g, taxonomy_term) do not have a uid.
+      // - If 'anonymous' is the author, don't allow access to History Tab,
+      //   since anyone can access it, and it will be published in Search engines.
+      $roles = array_merge(array(WORKFLOW_ROLE_AUTHOR_RID), $roles);
+    }
+
+    // Set up an array with states - they are already properly sorted.
+    // Unfortunately, the config_transitions are not sorted.
+    // Also, $transitions does not contain the 'stay on current state' transition.
+    // The allowed objects will be replaced with names.
+    $transitions = $workflow->getTransitionsBySid($current_sid, $roles);
+
+    // Let custom code add/remove/alter the available transitions.
+    // Using the new drupal_alter.
+    // Modules may veto a choice by removing a transition from the list.
+    $context = array(
+      'entity_type' => $entity_type,
+      'entity' => $entity,
+      'field_name' => $field_name,
+      'force' => $force,
+      'workflow' => $workflow,
+      'state' => $current_state,
+      'user' => $user,
+      'user_roles' => $roles, // @todo: can be removed in D8, since $user is in.
+    );
+    // @todo D8: rename to 'workflow_permitted_transitions'.
+    drupal_alter('workflow_permitted_state_transitions', $transitions, $context);
+
+    // Let custom code change the options, using old_style hook.
+    // @todo D8: delete below foreach/hook for better performance and flexibility.
+    // Above drupal_alter() calls hook_workflow_permitted_state_transitions_alter() only once.
+    foreach ($transitions as $transition) {
+      $new_sid = $transition->target_sid;
+      $permitted = array();
+
+      // We now have a list of config_transitions. Check each against the Entity.
+      // Invoke a callback indicating that we are collecting state choices.
+      // Modules may veto a choice by returning FALSE.
+      // In this case, the choice is never presented to the user.
+      if ($roles != 'ALL') {
+        $permitted = module_invoke_all('workflow', 'transition permitted', $current_sid, $new_sid, $entity, $force, $entity_type, $field_name, $transition, $user);
+      }
+
+      // If vetoed by a module, remove from list.
+      if (in_array(FALSE, $permitted, TRUE)) {
+        unset($transitions[$transition->tid]);
+      }
+    }
+
+    return $transitions;
+  }
+
+  /**
+   * Returns the allowed values for the current state.
+   *
+   * @param string $entity_type
+   *   The type of the entity at hand.
+   * @param object $entity
+   *   The entity at hand. May be NULL (E.g., on a Field settings page).
+   * @param $field_name
+   * @param $user
+   * @param bool $force
+   *
+   * @return array
+   *   An array of sid=>label pairs.
+   *   If $this->sid is set, returns the allowed transitions from this state.
+   *   If $this->sid is 0 or FALSE, then labels of ALL states of the State's
+   *   Workflow are returned.
+   *
+   * D7.x-2.x: deprecated workflow_field_choices() --> WorkflowState->getOptions()
+   */
+  public function getOptions($entity_type, $entity, $field_name, $user, $force = FALSE) {
+    // Define an Entity-specific cache per page load.
+    static $cache = array();
+
+    $options = array();
+
+    $entity_id = ($entity) ? entity_id($entity_type, $entity) : '';
+    $current_sid = $this->sid;
+
+    // Get options from page cache, using a non-empty index (just to be sure).
+    $entity_index = (!$entity) ? 'x' : $entity_id;
+    if (isset($cache[$entity_type][$entity_index][$force][$current_sid])) {
+      $options = $cache[$entity_type][$entity_index][$force][$current_sid];
+      return $options;
+    }
+
+    $workflow = $this->getWorkflow();
+    if (!$workflow) {
+      // No workflow, no options ;-)
+    }
+    elseif (!$current_sid) {
+      // If no State ID is given, we return all states.
+      // We cannot use getTransitions, since there are no ConfigTransitions
+      // from State with ID 0, and we do not want to repeat States.
+      foreach ($workflow->getStates() as $state) {
+        $options[$state->value()] = $state->label(); // Translation is done later.
+      }
+    }
+    else {
+      /* @var $transition WorkflowTransition */
+      $transitions = $this->getTransitions($entity_type, $entity, $field_name, $user, $force);
+      foreach ($transitions as $transition) {
+        // Get the label of the transition, and if empty of the target state.
+        // Beware: the target state may not exist, since it can be invented
+        // by custom code in the above drupal_alter() hook.
+        if (!$label = $transition->label()) {
+          $target_state = $transition->getNewState();
+          $label = $target_state ? $target_state->label() : '';
+        }
+        $new_sid = $transition->target_sid;
+        $options[$new_sid] = $label; // Translation is done later.
+      }
+
+      // Include current state for same-state transitions, except when $sid = 0.
+      // Caveat: this unnecessary since 7.x-2.3 (where stay-on-state transitions are saved, too.)
+      // but only if the transitions have been saved at least one time.
+      if ($current_sid && ($current_sid != $workflow->getCreationSid())) {
+        if (!isset($options[$current_sid])) {
+          $options[$current_sid] = $this->label(); // Translation is done later.
+        }
+      }
+
+      // Properly fix the labels.
+      // Translate, convert '&', make secure.
+      foreach($options as $key => $label) {
+        $options[$key] = html_entity_decode(check_plain(t($label)));
+      }
+
+      // Save to entity-specific cache.
+      $cache[$entity_type][$entity_index][$force][$current_sid] = $options;
+    }
+
+    return $options;
+  }
+
+  /**
+   * Returns the number of entities with this state.
+   *
+   * @return int
+   *   Counted number.
+   *
+   * @todo: add $options to select on entity type, etc.
+   */
+  public function count() {
+    $sid = $this->sid;
+    // Get the numbers for Workflow Node.
+    $result = db_select('workflow_node', 'wn')
+      ->fields('wn')
+      ->condition('sid', $sid, '=')
+      ->execute();
+    $count = count($result->fetchAll()); // @see #2285983 for using SQLite.
+
+    // Get the numbers for Workflow Field.
+    $fields = _workflow_info_fields();
+    foreach ($fields as $field_name => $field_map) {
+      if ($field_map['type'] == 'workflow') {
+        $query = new EntityFieldQuery();
+        $query
+          ->fieldCondition($field_name, 'value', $sid, '=')
+          // ->entityCondition('bundle', 'article')
+          // ->addMetaData('account', user_load(1)) // Run the query as user 1.
+          ->count(); // We only need the count.
+
+        $result = $query->execute();
+        $count += $result;
+      }
+    }
+
+    return $count;
+  }
+
+  /**
+   * Mimics Entity API functions.
+   */
+  protected function defaultLabel() {
+    return $this->state;
+  }
+
+  public function getName() {
+    return isset($this->name) ? $this->name : '';
+  }
+  public function setName($name) {
+    return $this->name = $name;
+  }
+  public function value() {
+    return $this->sid;
+  }
+
+  public function save() {
+    parent::save();
+
+    // Ensure Workflow is marked overridden.
+    $workflow = $this->getWorkflow();
+    if ($workflow->status == ENTITY_IN_CODE) {
+      $workflow->status = ENTITY_OVERRIDDEN;
+      $workflow->save();
+    }
+  }
+}
+
+class WorkflowStateController extends EntityAPIController {
+
+  public function save($entity, DatabaseTransaction $transaction = NULL) {
+    // Create the machine_name.
+    if (empty($entity->name)) {
+      if ($label = $entity->state) {
+        $entity->name = str_replace(' ', '_', strtolower($label));
+      }
+      else {
+        $entity->name = 'state_' . $entity->sid;
+      }
+    }
+
+    $return = parent::save($entity, $transaction);
+    if ($return) {
+      $workflow = $entity->getWorkflow();
+      // Maintain the new object in the workflow.
+      $workflow->states[$entity->sid] = $entity;
+    }
+
+    // Reset the cache for the affected workflow.
+    workflow_reset_cache($entity->wid);
+
+    return $return;
+  }
+
+  public function delete($ids, DatabaseTransaction $transaction = NULL) {
+    // @todo: replace with parent.
+    foreach ($ids as $id) {
+      if ($state = workflow_state_load($id)) {
+        $wid = $state->wid;
+        db_delete('workflow_states')
+          ->condition('sid', $state->sid)
+          ->execute();
+
+        // Reset the cache for the affected workflow.
+        workflow_reset_cache($wid);
+      }
+    }
+  }
+
+}

+ 737 - 0
sites/all/modules/contrib/admin/workflow/includes/Entity/WorkflowTransition.php

@@ -0,0 +1,737 @@
+<?php
+
+/**
+ * @file
+ * Contains workflow\includes\Entity\WorkflowTransition.
+ * Contains workflow\includes\Entity\WorkflowTransitionController.
+ *
+ * Implements (scheduled/executed) state transitions on entities.
+ */
+
+/**
+ * Implements an actual Transition.
+ *
+ * If a transition is executed, the new state is saved in the Field or {workflow_node}.
+ * If a transition is saved, it is saved in table {workflow_history_node}
+ */
+class WorkflowTransition extends Entity {
+  // Field data.
+  public $entity_type;
+  public $field_name = '';
+  public $language = LANGUAGE_NONE;
+  public $delta = 0;
+  // Entity data.
+  public $revision_id;
+  public $entity_id; // Use WorkflowTransition->getEntity() to fetch this.
+  public $nid; // @todo D8: remove $nid, use $entity_id. (requires conversion of Views displays.)
+  // Transition data.
+  // public $hid = 0;
+  public $wid = 0;
+  public $old_sid = 0;
+  public $new_sid = 0;
+  public $sid = 0; // @todo D8: remove $sid, use $new_sid. (requires conversion of Views displays.)
+  public $uid = 0; // Use WorkflowTransition->getUser() to fetch this.
+  public $stamp;
+  public $comment = '';
+  // Cached data, from $this->entity_id and $this->uid.
+  protected $entity = NULL; // Use WorkflowTransition->getEntity() to fetch this.
+  protected $user = NULL; // Use WorkflowTransition->getUser() to fetch this.
+  // Extra data.
+  protected $is_scheduled = NULL;
+  protected $is_executed = NULL;
+  protected $force = NULL;
+
+  /**
+   * Entity class functions.
+   */
+
+  /**
+   * Creates a new entity.
+   *
+   * @param array $values
+   *   The initial values.
+   * @param string $entityType
+   *   The entity type of this Entity subclass.
+   *
+   * @see entity_create()
+   *
+   * No arguments passed, when loading from DB.
+   * All arguments must be passed, when creating an object programmatically.
+   * One argument $entity may be passed, only to directly call delete() afterwards.
+   */
+  public function __construct(array $values = array(), $entityType = 'WorkflowTransition') {
+    // Please be aware that $entity_type and $entityType are different things!
+    parent::__construct($values, $entityType);
+
+    $this->hid = isset($this->hid) ? $this->hid : 0;
+    // This transition is not scheduled
+    $this->is_scheduled = FALSE;
+    // This transition is not executed, if it has no hid, yet, upon load.
+    $this->is_executed = ($this->hid > 0);
+
+    // Fill the 'new' fields correctly. @todo D8: rename these fields in db table.
+    $this->entity_id = $this->nid;
+    $this->new_sid = $this->sid;
+    // Initialize wid, if not set.
+    if ($this->old_sid && !$this->wid) {
+      $this->getWorkflow();
+    }
+  }
+
+  /**
+   * Helper function for __construct. Used for all children of WorkflowTransition (aka WorkflowScheduledTransition)
+   *
+   * @param $entity_type
+   * @param $entity
+   * @param $field_name
+   * @param $old_sid
+   * @param $new_sid
+   * @param null $uid
+   * @param int $stamp
+   * @param string $comment
+   */
+  public function setValues($entity_type, $entity, $field_name, $old_sid, $new_sid, $uid = NULL, $stamp = REQUEST_TIME, $comment = '') {
+    // Normally, the values are passed in an array, and set in parent::__construct, but we do it ourselves.
+    // (But there is no objection to do it there.)
+
+    global $user;
+
+    $this->entity_type = (!$entity_type) ? $this->entity_type : $entity_type;
+    $this->field_name = (!$field_name) ? $this->field_name : $field_name;
+    $uid = ($uid === NULL) ? $user->uid : $uid;
+
+    // If constructor is called with new() and arguments.
+    // Load the supplied entity.
+    if ($entity && !$entity_type) {
+      // Not all parameters are passed programmatically.
+      drupal_set_message(t('Wrong call to new Workflow*Transition()'), 'error');
+    }
+    elseif ($entity) {
+      $this->setEntity($entity_type, $entity);
+    }
+
+    if (!$entity && !$old_sid && !$new_sid) {
+      // If constructor is called without arguments, e.g., loading from db.
+    }
+    elseif ($entity && $old_sid) {
+      // Caveat: upon entity_delete, $new_sid is '0'.
+      // If constructor is called with new() and arguments.
+      $this->old_sid = $old_sid;
+      $this->sid = $new_sid;
+
+      $this->uid = $uid;
+      $this->stamp = $stamp;
+      $this->comment = $comment;
+
+      // Set language. Multi-language is not supported for Workflow Node.
+      $this->language = _workflow_metadata_workflow_get_properties($entity, array(), 'langcode', $entity_type, $field_name);
+    }
+    elseif (!$old_sid) {
+      // Not all parameters are passed programmatically.
+      drupal_set_message(
+        t('Wrong call to constructor Workflow*Transition(@old_sid to @new_sid)', array('@old_sid' => $old_sid, '@new_sid' => $new_sid)),
+        'error');
+    }
+
+    // Fill the 'new' fields correctly. @todo D8: rename these fields in db table.
+    $this->entity_id = $this->nid;
+    $this->new_sid = $this->sid;
+    // Initialize wid, if not set.
+    if ($this->old_sid && !$this->wid) {
+      $this->getWorkflow();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function defaultLabel() {
+    // @todo; Should return title of WorkflowConfigTransition. Make it a superclass??
+    return t('Workflow transition !hid', array('!hid' =>3));
+  }
+
+//  protected function defaultUri() {
+//    return array('path' => 'workflow_transition/' . $this->hid);
+//  }
+
+  /**
+   * CRUD functions.
+   */
+
+  /**
+   * Given a node, get all transitions for it.
+   *
+   * Since this may return a lot of data, a limit is included to allow for only one result.
+   *
+   * @param string $entity_type
+   * @param array $entity_ids
+   * @param string $field_name
+   *   Optional. Can be NULL, if you want to load any field.
+   * @param null $limit
+   * @param string $langcode
+   *
+   * @return array
+   *   An array of WorkflowTransitions.
+   */
+  public static function loadMultiple($entity_type, array $entity_ids, $field_name = '', $limit = NULL, $langcode = '') {
+    $query = db_select('workflow_node_history', 'h');
+    $query->condition('h.entity_type', $entity_type);
+    if ($entity_ids) {
+      $query->condition('h.nid', $entity_ids);
+    }
+    if ($field_name !== NULL) {
+      // If we do not know/care for the field_name, fetch all history.
+      // E.g., in workflow.tokens.
+      $query->condition('h.field_name', $field_name);
+    }
+    // Add selection on language.
+    // Workflow Node: only has 'und'.
+    // Workflow Field: untranslated field have 'und'.
+    // Workflow Field: translated fields may be specified.
+    if ($langcode) {
+      $query->condition('h.language', $langcode);
+    }
+
+    $query->fields('h');
+    // The timestamp is only granular to the second; on a busy site, we need the id.
+    // $query->orderBy('h.stamp', 'DESC');
+    $query->orderBy('h.hid', 'DESC');
+    if ($limit) {
+      $query->range(0, $limit);
+    }
+    $result = $query->execute()->fetchAll(PDO::FETCH_CLASS, 'WorkflowTransition');
+
+    return $result;
+  }
+
+  /**
+   * Property functions.
+   */
+
+  /**
+   * Verifies if the given transition is allowed.
+   *
+   * - In settings;
+   * - In permissions;
+   * - By permission hooks, implemented by other modules.
+   *
+   * @param $roles
+   * @param $user
+   * @param $force
+   *
+   * @return bool TRUE if OK, else FALSE.
+   *   TRUE if OK, else FALSE.
+   *
+   * Having both $roles AND $user seems redundant, but $roles have been
+   * tampered with, even though they belong to the $user.
+   * @see WorkflowConfigTransition::isAllowed()
+   */
+  protected function isAllowed($roles, $user, $force) {
+    if ($force || ($user->uid == 1)) {
+      return TRUE;
+    }
+
+    // Check allow-ability of state change if user is not superuser (might be cron).
+    // Get the WorkflowConfigTransition.
+    // @todo: some day, WorkflowConfigTransition can be a parent of WorkflowTransition.
+    $workflow = $this->getWorkflow();
+    $config_transitions = $workflow->getTransitionsBySidTargetSid($this->old_sid, $this->new_sid);
+    $config_transition = reset($config_transitions);
+    if (!$config_transition || !$config_transition->isAllowed($roles)) {
+      $t_args = array(
+        '%old_sid' => $this->old_sid,
+        '%new_sid' => $this->new_sid,
+      );
+      watchdog('workflow', 'Attempt to go to nonexistent transition (from %old_sid to %new_sid)', $t_args, WATCHDOG_ERROR);
+      return FALSE;
+    }
+
+    return TRUE;
+  }
+
+  /**
+   * Execute a transition (change state of a node).
+   *
+   * @param bool $force
+   *   If set to TRUE, workflow permissions will be ignored.
+   *
+   * @return int
+   *   New state ID. If execution failed, old state ID is returned,
+   *
+   * deprecated workflow_execute_transition() --> WorkflowTransition::execute().
+   */
+  public function execute($force = FALSE) {
+    $user = $this->getUser();
+    $old_sid = $this->old_sid;
+    $new_sid = $this->new_sid;
+
+    // Load the entity, if not already loaded.
+    // This also sets the (empty) $revision_id in Scheduled Transitions.
+    $entity = $this->getEntity();
+    // Only after getEntity(), the following are surely set.
+    $entity_type = $this->entity_type;
+    $entity_id = $this->entity_id;
+    $field_name = $this->field_name;
+
+    // Make sure $force is set in the transition, too.
+    if ($force) {
+      $this->force($force);
+    }
+    $force = $this->isForced();
+
+    // Prepare an array of arguments for error messages.
+    $args = array(
+      '%user' => isset($user->name) ? $user->name : '',
+      '%old' => $old_sid,
+      '%new' => $new_sid,
+    );
+
+    if (!$this->getOldState()) {
+      drupal_set_message($message = t('You tried to set a Workflow State, but
+        the entity is not relevant. Please contact your system administrator.'),
+        'error');
+      $message = 'Setting a non-relevant Entity from state %old to %new';
+      $uri = entity_uri($entity_type, $entity);
+      watchdog('workflow', $message, $args, WATCHDOG_ERROR, l('view', $uri['path']));
+      return $old_sid;
+    }
+
+    // Check if the state has changed.
+    $state_changed = ($old_sid != $new_sid);
+
+    // If so, check the permissions.
+    if ($state_changed) {
+      // State has changed. Do some checks upfront.
+
+      if (!$force) {
+        // Make sure this transition is allowed by workflow module Admin UI.
+        $roles = array_keys($user->roles);
+        $roles = array_merge(array(WORKFLOW_ROLE_AUTHOR_RID), $roles);
+        if (!$this->isAllowed($roles, $user, $force)) {
+          watchdog('workflow', 'User %user not allowed to go from state %old to %new', $args, WATCHDOG_NOTICE);
+          // If incorrect, quit.
+          return $old_sid;
+        }
+      }
+
+      if (!$force) {
+        // Make sure this transition is allowed by custom module.
+        // @todo D8: remove, or replace by 'transition pre'. See WorkflowState::getOptions().
+        // @todo D8: replace all parameters that are included in $transition.
+        $permitted = module_invoke_all('workflow', 'transition permitted', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name, $this, $user);
+        // Stop if a module says so.
+        if (in_array(FALSE, $permitted, TRUE)) {
+          watchdog('workflow', 'Transition vetoed by module.');
+          return $old_sid;
+        }
+      }
+
+      // Make sure this transition is valid and allowed for the current user.
+      // Invoke a callback indicating a transition is about to occur.
+      // Modules may veto the transition by returning FALSE.
+      // (Even if $force is TRUE, but they shouldn't do that.)
+      $permitted = module_invoke_all('workflow', 'transition pre', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name, $this);
+      // Stop if a module says so.
+      if (in_array(FALSE, $permitted, TRUE)) {
+        watchdog('workflow', 'Transition vetoed by module.');
+        return $old_sid;
+      }
+
+    }
+    elseif ($this->comment) {
+      // No need to ask permission for adding comments.
+      // Since you should not add actions to a 'transition pre' event, there is
+      // no need to invoke the event.
+    }
+    else {
+      // There is no state change, and no comment.
+      // We may need to clean up something.
+    }
+
+    if ($state_changed || $this->comment) {
+      // Store the transition, so it can be easily fetched later on.
+      // Store in an array, to prepare for multiple workflow_fields per entity.
+      // This is a.o. used in hook_entity_update to trigger 'transition post'.
+      // Only add the Transition once! or you will encounter endless loops in
+      // hook_entity_update() in workflow_actions_entity_update et all.
+      if (!isset($entity->workflow_transitions[$field_name])) {
+        $entity->workflow_transitions[$field_name] = &$this;
+      }
+
+      // The transition is allowed. Let other modules modify the comment.
+      // @todo D8: remove all but last items from $context.
+      $context = array(
+        'node' => $entity,
+        'sid' => $new_sid,
+        'old_sid' => $old_sid,
+        'uid' => $user->uid,
+        'transition' => $this,
+      );
+      drupal_alter('workflow_comment', $this->comment, $context);
+    }
+
+    // Now, change the database.
+
+    // Log the new state in {workflow_node}.
+    if (!$field_name) {
+      if ($state_changed || $this->comment) {
+        // If the node does not have an existing 'workflow' property,
+        // save the $old_sid there, so it can be logged.
+        if (!isset($entity->workflow)) { // This is a workflow_node sid.
+          $entity->workflow = $old_sid;  // This is a workflow_node sid.
+        }
+
+        // Change the state for {workflow_node}.
+        // The equivalent for Field API is in WorkflowDefaultWidget::submit.
+        $data = array(
+          'nid' => $entity_id,
+          'sid' => $new_sid,
+          'uid' => (isset($entity->workflow_uid) ? $entity->workflow_uid : $user->uid),
+          'stamp' => REQUEST_TIME,
+        );
+        workflow_update_workflow_node($data);
+
+        $entity->workflow = $new_sid;  // This is a workflow_node sid.
+      }
+    }
+    else {
+      // This is a Workflow Field.
+      // Until now, adding code here (instead of in workflow_execute_transition() )
+      // doesn't work, creating an endless loop.
+      // Update 10-dec-2016: the following line, added above, may have resolved that.
+      //     if (!isset($entity->workflow_transitions[$field_name]))
+/*
+      if ($state_changed || $this->comment) {
+        // Do a separate update to update the field (Workflow Field API)
+        // This will call hook_field_update() and WorkflowFieldDefaultWidget::submit().
+        // $entity->{$field_name}[$this->language] = array();
+        // $entity->{$field_name}[$this->language][0]['workflow']['workflow_sid'] = $new_sid;
+        // $entity->{$field_name}[$this->language][0]['workflow']['workflow_comment'] = $this->comment;
+        $entity->{$field_name}[$this->language][0]['transition'] = $this;
+
+        // Save the entity, but not through entity_save(),
+        // since this will check permissions again and trigger rules.
+        // @TODO: replace below by a workflow_field setter callback.
+        // The transition was successfully executed, or else a message was raised.
+//        entity_save($entity_type, $entity);
+        // or
+//        field_attach_update($entity_type, $entity);
+
+        // Reset the entity cache after update.
+        entity_get_controller($entity_type)->resetCache(array($entity_id));
+
+        $new_sid = workflow_node_current_state($entity, $entity_type, $field_name);
+      }
+ */
+    }
+
+    $this->is_executed = TRUE;
+
+    if ($state_changed || $this->comment) {
+
+      // Log the transition in {workflow_node_history}.
+      $this->save();
+
+      // Register state change with watchdog.
+      if ($state_changed) {
+        $workflow = $this->getWorkflow();
+        // Get the workflow_settings, unified for workflow_node and workflow_field.
+        // @todo D8: move settings back to Workflow (like workflownode currently is).
+        // @todo D8: to move settings back, grep for "workflow->options" and "field['settings']".
+        $field = _workflow_info_field($field_name, $workflow);
+
+        if (($new_state = $this->getNewState()) && !empty($field['settings']['watchdog_log'])) {
+          $entity_type_info = entity_get_info($entity_type);
+          $message = ($this->isScheduled()) ? 'Scheduled state change of @type %label to %state_name executed' : 'State of @type %label set to %state_name';
+          $args = array(
+            '@type' => $entity_type_info['label'],
+            '%label' => entity_label($entity_type, $entity),
+            '%state_name' => check_plain(t($new_state->label())),
+          );
+          $uri = entity_uri($entity_type, $entity);
+          watchdog('workflow', $message, $args, WATCHDOG_NOTICE, l('view', $uri['path']));
+        }
+      }
+
+      // Remove any scheduled state transitions.
+      foreach (WorkflowScheduledTransition::load($entity_type, $entity_id, $field_name) as $scheduled_transition) {
+        /* @var $scheduled_transition WorkflowScheduledTransition */
+        $scheduled_transition->delete();
+      }
+
+      // Notify modules that transition has occurred.
+      // Action triggers should take place in response to this callback, not the 'transaction pre'.
+      if (!$field_name) {
+        // Now that workflow data is saved, reset stuff to avoid problems
+        // when Rules etc want to resave the data.
+        // Remember, this is only for nodes, and node_save() is not necessarily performed.
+        unset($entity->workflow_comment);
+        module_invoke_all('workflow', 'transition post', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name, $this);
+        entity_get_controller('node')->resetCache(array($entity->nid)); // from entity_load(), node_save();
+      }
+      else {
+        // module_invoke_all('workflow', 'transition post', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name, $this);
+        // We have a problem here with Rules, Trigger, etc. when invoking
+        // 'transition post': the entity has not been saved, yet. we are still
+        // IN the transition, not AFTER. Alternatives:
+        // 1. Save the field here explicitly, using field_attach_save;
+        // 2. Move the invoke to another place: hook_entity_insert(), hook_entity_update();
+        // 3. Rely on the entity hooks. This works for Rules, not for Trigger.
+        // --> We choose option 2:
+        // - First, $entity->workflow_transitions[] is set for easy re-fetching.
+        // - Then, post_execute() is invoked via workflowfield_entity_insert(), _update().
+      }
+    }
+
+    return $new_sid;
+  }
+
+  /**
+   * Invokes 'transition post'.
+   *
+   * Add the possibility to invoke the hook from elsewhere.
+   */
+  public function post_execute($force = FALSE) {
+    $old_sid = $this->old_sid;
+    $new_sid = $this->new_sid;
+    $entity = $this->getEntity(); // Entity may not be loaded, yet.
+    $entity_type = $this->entity_type;
+    // $entity_id = $this->entity_id;
+    $field_name = $this->field_name;
+
+    $state_changed = ($old_sid != $new_sid);
+    if ($state_changed || $this->comment) {
+      module_invoke_all('workflow', 'transition post', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name, $this);
+    }
+  }
+
+
+  /**
+   * Get the Transitions $workflow.
+   *
+   * @return Workflow|NULL
+   *   The workflow for this Transition.
+   */
+  public function getWorkflow() {
+    $workflow = NULL;
+    if (!$this->wid) {
+      $state = workflow_state_load_single($this->new_sid ? $this->new_sid : $this->old_sid);
+      $this->wid = (int) $state->wid;
+    }
+    if ($this->wid) {
+      $workflow = workflow_load($this->wid);
+    }
+    return $workflow;
+  }
+
+  /**
+   * Get the Transitions $entity.
+   *
+   * @return object
+   *   The entity, that is added to the Transition.
+   */
+  public function getEntity() {
+    if (empty($this->entity) && $this->entity_type) {
+      $entity_type = $this->entity_type;
+      $entity_id = $this->entity_id;
+      $entity = entity_load_single($entity_type, $entity_id);
+
+      // Set the entity cache.
+      $this->entity = $entity;
+
+      // Make sure the vid of Entity and Transition are equal.
+      // Especially for Scheduled Transition, that do not have this set, yet,
+      // or may have an outdated revision ID.
+      $info = entity_get_info($entity_type);
+      $revision_key = $info['entity keys']['revision'];
+      $this->revision_id = (isset($entity->{$revision_key})) ? $entity->{$revision_key} : NULL;
+    }
+
+    return $this->entity;
+  }
+
+  /**
+   * Set the Transitions $entity.
+   *
+   * @param string $entity_type
+   *   The entity type of the entity.
+   * @param mixed $entity
+   *   The Entity ID or the Entity object, to add to the Transition.
+   *
+   * @return object $entity
+   *   The Entity, that is added to the Transition.
+   */
+  public function setEntity($entity_type, $entity) {
+    if (!is_object($entity)) {
+      $entity_id = $entity;
+      // Use node API or Entity API to load the object first.
+      $entity = entity_load_single($entity_type, $entity_id);
+    }
+    $this->entity = $entity;
+    $this->entity_type = $entity_type;
+    list($this->entity_id, $this->revision_id,) = entity_extract_ids($entity_type, $entity);
+
+    // For backwards compatibility, set nid.
+    $this->nid = $this->entity_id;
+
+    return $this->entity;
+  }
+
+  public function getUser() {
+    if (!isset($this->user) || ($this->user->uid != $this->uid)) {
+      $this->user = user_load($this->uid);
+    }
+    return $this->user;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFieldName() {
+    return $this->field_name;
+  }
+
+  /**
+   * Functions, common to the WorkflowTransitions.
+   */
+  public function getOldState() {
+    return workflow_state_load_single($this->old_sid);
+  }
+  public function getNewState() {
+    return workflow_state_load_single($this->new_sid);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getComment() {
+    return $this->comment;
+  }
+
+  /**
+   * Returns the time on which the transitions was or will be executed.
+   *
+   * @return mixed
+   */
+  public function getTimestamp() {
+    return $this->stamp;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTimestampFormatted() {
+    $timestamp = $this->stamp;
+    return format_date($timestamp);;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setTimestamp($value) {
+    $this->stamp = $value;
+    return $this;
+  }
+
+  /**
+   * Returns if this is a Scheduled Transition.
+   */
+  public function isScheduled() {
+    return $this->is_scheduled;
+  }
+  public function schedule($schedule = TRUE) {
+    return $this->is_scheduled = $schedule;
+  }
+
+  public function isExecuted() {
+    return $this->is_executed;
+  }
+
+  /**
+   * A transition may be forced skipping checks.
+   */
+  public function isForced() {
+    return (bool) $this->force;
+  }
+  public function force($force = TRUE) {
+    return $this->force = $force;
+  }
+
+  /**
+   * Helper debugging function to easily show the contents fo a transition.
+   */
+  public function dpm($function = '') {
+    $transition = $this;
+    $entity = $transition->getEntity();
+    $entity_type = $transition->entity_type;
+    list($entity_id, , $entity_bundle) = ($entity) ? entity_extract_ids($entity_type, $entity) : array('', '', '');
+    $time = $transition->getTimestampFormatted();
+    // Do this extensive $user_name lines, for some troubles with Action.
+    $user = $transition->getUser();
+    $user_name = ($user) ? $user->name : 'unknown username';
+    $t_string = get_class($this) . ' ' . (isset($this->hid) ? $this->hid : '') . ' ' . ($function ? ("in function '$function'") : '');
+    $output[] = 'Entity  = ' . ((!$entity) ? 'NULL' : ($entity_type . '/' . $entity_bundle . '/' . $entity_id));
+    $output[] = 'Field   = ' . $transition->getFieldName();
+    $output[] = 'From/To = ' . $transition->old_sid . ' > ' . $transition->new_sid . ' @ ' . $time;
+    $output[] = 'Comment = ' . $user_name . ' says: ' . $transition->getComment();
+    $output[] = 'Forced  = ' . ($transition->isForced() ? 'yes' : 'no');
+    if (function_exists('dpm')) { dpm($output, $t_string); }
+  }
+
+}
+
+/**
+ * Implements a controller class for WorkflowTransition.
+ *
+ * The 'true' controller class is 'Workflow'.
+ */
+class WorkflowTransitionController extends EntityAPIController {
+
+  /**
+   * Overrides DrupalDefaultEntityController::cacheGet().
+   *
+   * Override default function, due to core issue #1572466.
+   */
+  protected function cacheGet($ids, $conditions = array()) {
+    // Load any available entities from the internal cache.
+    if ($ids === FALSE && !$conditions) {
+      return $this->entityCache;
+    }
+    return parent::cacheGet($ids, $conditions);
+  }
+
+  /**
+   * Insert (no update) a transition.
+   *
+   * deprecated workflow_insert_workflow_node_history() --> WorkflowTransition::save()
+   */
+  public function save($entity, DatabaseTransaction $transaction = NULL) {
+    // Check for no transition.
+    if ($entity->old_sid == $entity->new_sid) {
+      if (!$entity->comment) {
+        // Write comment into history though.
+        return;
+      }
+    }
+
+    if (empty($entity->hid)) {
+      // Insert the transition. Make sure it hasn't already been inserted.
+      $last_history = workflow_transition_load_single($entity->entity_type, $entity->entity_id, $entity->field_name, $entity->language);
+      if ($last_history &&
+          $last_history->stamp == REQUEST_TIME &&
+          $last_history->new_sid == $entity->new_sid) {
+        return;
+      }
+      else {
+        unset($entity->hid);
+        $entity->stamp = isset($entity->stamp) ? $entity->stamp : REQUEST_TIME;
+
+        return parent::save($entity, $transaction);
+      }
+    }
+    else {
+      // Update the transition.
+      return parent::save($entity, $transaction);
+    }
+  }
+}

+ 15 - 0
sites/all/modules/contrib/admin/workflow/includes/Entity/WorkflowTransitionController.php

@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Contains a stub class. 
+ * The functionality is removed but apparently caches aren't refreshed 
+ * properly during upgrade. 
+ * @see https://www.drupal.org/node/2620530
+ */
+
+/**
+ *
+ */
+class WorkflowTransitionController {
+}

+ 64 - 0
sites/all/modules/contrib/admin/workflow/includes/Field/WorkflowD7Base.php

@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ * Contains workflow\includes\Field\WorkflowD7Base.
+ */
+
+/*
+ * A Retrofit/Stub class, that contains the most basic functions of the D8 WidgetBase class.
+ * It serves as a superclass to containe the $field and $instance array structures for the Field Type and the Widget.
+ * @todo D8: Remove this class.
+ */
+abstract class WorkflowD7Base {
+  // Properties for Field and Widget.
+  protected $field = array();
+  protected $instance = array();
+  // Properties for Field.
+  protected $entity = NULL;
+  protected $entity_type = '';
+
+  /**
+   * Constructor, stub for D8 WidgetBase.
+   */
+  public function __construct(array $field, array $instance, $entity_type = '', $entity = NULL) {
+    if (!empty($entity) && !is_object($entity)) {
+      throw new Exception('Entity should be an object.');
+    }
+
+    // Properties for Widget and Field.
+    $this->field = $field;
+    $this->instance = $instance;
+    // Properties for FieldItem.
+    $this->entity = $entity;
+    $this->entity_type = $entity_type;
+  }
+
+  public function getField() {
+    return $this->field;
+  }
+
+  public function getInstance() {
+    return $this->instance;
+  }
+
+  public function delete() {
+  }
+
+  protected function getSettings() {
+    $settings = isset($this->instance['widget']['settings']) ? $this->instance['widget']['settings'] : array();
+    $field_info = self::settings();
+    return $settings += $field_info['workflow']['settings'];
+  }
+
+  protected function getSetting($key) {
+    if (isset($this->instance['widget']['settings'][$key])) {
+      return $this->instance['widget']['settings'][$key];
+    }
+    else {
+      $field_info = $this->settings();
+      return $field_info['workflow']['settings'][$key];
+    }
+  }
+
+}

+ 107 - 0
sites/all/modules/contrib/admin/workflow/includes/Field/WorkflowDefaultWidget.php

@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * @file
+ * Contains workflow\includes\Field\WorkflowDefaultWidget.
+ */
+
+/**
+ * Plugin implementation of the 'workflow_default' widget.
+ */
+class WorkflowDefaultWidget extends WorkflowD7Base { // D8: extends WidgetBase {
+
+  /**
+   * Returns the settings.
+   *
+   * @todo d8: Replace by the 'annotations' in D8 (See comments above this class).
+   */
+  public static function settings() {
+    return array(
+      'workflow_default' => array(
+        'label' => t('Workflow'),
+        'field types' => array('workflow'),
+        'settings' => array(
+          'name_as_title' => 1,
+          'fieldset' => 0,
+          'comment' => 1,
+        ),
+      ),
+    );
+  }
+
+  /**
+   * Implements hook_field_widget_settings_form() --> WidgetInterface::settingsForm().
+   *
+   * {@inheritdoc}
+   *
+   * The Widget Instance has no settings. To have a uniform UX, all settings are done on the Field level.
+   */
+  public function settingsForm(array $form, array &$form_state, $has_data) {
+    $element = array();
+    return $element;
+  }
+
+  /**
+   * Implements hook_field_widget_form --> WidgetInterface::formElement().
+   *
+   * {@inheritdoc}
+   *
+   * Be careful: Widget may be shown in very different places. Test carefully!!
+   *  - On a entity add/edit page
+   *  - On a entity preview page
+   *  - On a entity view page
+   *  - On a entity 'workflow history' tab
+   *  - On a comment display, in the comment history
+   *  - On a comment form, below the comment history
+   *
+   * @todo D8: change "array $items" to "FieldInterface $items"
+   */
+  public function formElement(array $items, $delta, array $element, array &$form, array &$form_state) {
+    $field = $this->field;
+    $instance = $this->instance;
+    $entity = $this->entity;
+    $entity_type = $this->entity_type;
+
+    // Add the element. Do not use drupal_get_form, or you will have a form in a form.
+    workflow_transition_form($form, $form_state, $field, $instance, $entity_type, $entity);
+  }
+
+  /**
+   * Implements workflow_transition() -> WorkflowDefaultWidget::submit().
+   *
+   * Overrides submit(array $form, array &$form_state).
+   * Contains 2 extra parameters for D7
+   *
+   * @param array $form
+   * @param array $form_state
+   * @param array $items
+   *   The value of the field.
+   * @param bool $force
+   *   TRUE if all access must be overridden, e.g., for Rules.
+   *
+   * @return int
+   *   If update succeeded, the new State Id. Else, the old Id is returned.
+   *
+   * This is called from function _workflowfield_form_submit($form, &$form_state)
+   * It is a replacement of function workflow_transition($node, $new_sid, $force, $field)
+   * It performs the following actions;
+   * - save a scheduled action
+   * - update history
+   * - restore the normal $items for the field.
+   * @todo: remove update of {node_form} table. (separate task, because it has features, too)
+   */
+  public function submit(array $form, array &$form_state, array &$items, $force = FALSE) {
+    return workflow_transition_form_submit($form, $form_state);
+  }
+
+  /**
+   * Implements hook_field_widget_error --> WidgetInterface::errorElement().
+   */
+  // public function errorElement(array $element, ConstraintViolationInterface $violation, array $form, array &$form_state) {
+  // }
+  // public function settingsSummary() {
+  // }
+  // public function massageFormValues(array $values, array $form, array &$form_state) {
+  // }
+
+}

+ 329 - 0
sites/all/modules/contrib/admin/workflow/includes/Field/WorkflowItem.php

@@ -0,0 +1,329 @@
+<?php
+
+/**
+ * @file
+ * Contains workflow\includes\Field\WorkflowItem.
+ * @see https://drupal.org/node/2064123 for 'Field Type Plugin' change record D7->D8.
+ */
+
+/**
+ * Plugin implementation of the 'workflow' field type.
+ *
+ * @FieldType(
+ *   id = "workflow",
+ *   label = @Translation("Workflow"),
+ *   description = @Translation("This field stores Workflow values for a certain Workflow type from a list of allowed 'value => label' pairs, i.e. 'Publishing': 1 => unpublished, 2 => draft, 3 => published."),
+ *   default_widget = "options_select",
+ *   default_formatter = "list_formatter",
+ *   property_type' = WORKFLOWFIELD_PROPERTY_TYPE,
+ * )
+ */
+class WorkflowItem extends WorkflowD7Base {// D8: extends ConfigFieldItemBase implements PrepareCacheInterface {
+  /**
+   * Function, that gets replaced by the 'annotations' in D8. (@see comments above this class)
+   */
+  public static function getInfo() {
+    return array(
+      'workflow' => array(
+        'label' => t('Workflow'),
+        'description' => t("This field stores Workflow values for a certain Workflow type from a list of allowed 'value => label' pairs, i.e. 'Publishing': 1 => unpublished, 2 => draft, 3 => published."),
+        'settings' => array(
+          'allowed_values_function' => 'workflowfield_allowed_values', // For the list.module formatter
+          // 'allowed_values_function' => 'WorkflowItem::getAllowedValues', // For the list.module formatter.
+          'wid' => '',
+          // 'history' => 1,
+          // 'schedule' => 0,
+          // 'comment' => 0,
+          'widget' => array(
+            'options' => 'select',
+            'name_as_title' => 1,
+            'fieldset' => 0,
+            'hide' => 0,
+            'schedule' => 1,
+            'schedule_timezone' => 1,
+            'comment' => 1,
+          ),
+          'watchdog_log' => 1,
+          'history' => array(
+            'history_tab_show' => 1,
+            'roles' => array(),
+          ),
+        ),
+        'instance_settings' => array(),
+        'default_widget' => 'workflow',
+        'default_formatter' => 'list_default',
+        // Properties are introduced in Entity API and used for Rules integration.
+        'property_type' => WORKFLOWFIELD_PROPERTY_TYPE,
+        'property_callbacks' => array('workflowfield_property_info_callback'),
+      ),
+    );
+  }
+
+  /**
+   * Implements hook_field_settings_form() -> ConfigFieldItemInterface::settingsForm().
+   *
+   * @param array $form
+   * @param array $form_state
+   * @param $has_data
+   *
+   * @return array $element
+   *   The newly constructed element.
+   */
+  public function settingsForm(array $form, array &$form_state, $has_data) {
+    $field_info = self::getInfo();
+    $settings = $this->field['settings'];
+    $settings += $field_info['workflow']['settings'];
+    $settings['widget'] += $field_info['workflow']['settings']['widget'];
+
+    // Create list of all Workflow types. Include an initial empty value.
+    // Validate each workflow, and generate a message if not complete.
+    /* @var $workflow Workflow */
+    $workflows = array();
+    $workflows[''] = t('- Select a value -');
+    foreach ($workflows += workflow_get_workflow_names() as $wid => $label) {
+      $workflow = workflow_load_single($wid);
+      if ($wid && !$workflow->isValid()) {
+        unset($workflows[$wid]);
+      }
+    }
+
+    // Set message, if no 'validated' workflows exist.
+    if (count($workflows) == 1) {
+      drupal_set_message(
+        t('You must create at least one workflow before content can be
+          assigned to a workflow.')
+      );
+    }
+
+    // The allowed_values_functions is used in the formatter from list.module.
+    $element['allowed_values_function'] = array(
+      '#type' => 'value',
+      '#value' => $settings['allowed_values_function'], // = 'workflowfield_allowed_values',
+    );
+
+    // $field['settings']['wid'] can be numeric or named, or empty.
+    $wid = isset($settings['wid']) ? $settings['wid'] : '';
+    // Let the user choose between the available workflow types.
+    $element['wid'] = array(
+      '#type' => 'select',
+      '#title' => t('Workflow type'),
+      '#options' => $workflows,
+      '#default_value' => $wid,
+      '#required' => TRUE,
+      '#disabled' => $has_data,
+      '#description' => t('Choose the Workflow type. Maintain workflows !url.', array('!url' => l(t('here'), 'admin/config/workflow/workflow'))),
+    );
+
+    // Inform the user of possible states.
+    // If no Workflow type is selected yet, do not show anything.
+    if ($wid) {
+      // Get a string representation to show all options.
+      $allowed_values = workflow_state_load_multiple($wid);
+      $allowed_values_string = $this->_allowed_values_string($wid);
+
+      $element['allowed_values_string'] = array(
+        '#type' => 'textarea',
+        '#title' => t('Allowed values for the selected Workflow type'),
+        '#default_value' => $allowed_values_string,
+        '#rows' => count($allowed_values),
+        '#access' => TRUE, // User can see the data,
+        '#disabled' => TRUE, // .. but cannot change them.
+      );
+    }
+
+    $element['widget'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Workflow widget'),
+      '#description' => t('Set some global properties of the widgets for this
+        workflow. Some can be altered per widget instance.'
+      ),
+    );
+    $fieldset_options = array(0 => t('No fieldset'), 1 => t('Collapsible fieldset'), 2 => t('Collapsed fieldset'));
+    $element['widget']['fieldset'] = array(
+      '#type' => 'select',
+      '#options' => $fieldset_options,
+      '#title' => t('Show the form in a fieldset?'),
+      '#default_value' => $settings['widget']['fieldset'],
+      '#description' => t("The Widget can be wrapped in a visible fieldset. You'd
+        do this when you use the widget on a Node Edit page."
+      ),
+    );
+    $element['widget']['options'] = array(
+      '#type' => 'select',
+      '#title' => t('How to show the available states'),
+      '#required' => FALSE,
+      '#default_value' => $settings['widget']['options'],
+      // '#multiple' => TRUE / FALSE,
+      '#options' => array(
+        // These options are taken from options.module
+        'select' => 'Select list',
+        'radios' => 'Radio buttons',
+        // This option does not work properly on Comment Add form.
+        'buttons' => 'Action buttons',
+      ),
+      '#description' => t("The Widget shows all available states. Decide which
+        is the best way to show them. ('Action buttons' do not work on Comment form.)"
+      ),
+    );
+    $element['widget']['hide'] = array(
+      '#type' => 'checkbox',
+      '#attributes' => array('class' => array('container-inline')),
+      '#title' => t('Hide the widget on Entity form.'),
+      '#default_value' => $settings['widget']['hide'],
+      '#description' => t(
+        'Using Workflow Field, the widget is always shown when editing an
+        Entity. Set this checkbox in case you only want to change the status
+        on the Workflow History tab or on the Node View. (This checkbox is
+        only needed because Drupal core does not have a "hidden" widget.)'
+      ),
+    );
+    $element['widget']['name_as_title'] = array(
+      '#type' => 'checkbox',
+      '#attributes' => array('class' => array('container-inline')),
+      '#title' => t('Use the workflow name as the title of the workflow form'),
+      '#default_value' => $settings['widget']['name_as_title'],
+      '#description' => t(
+        'The workflow section of the editing form is in its own fieldset.
+         Checking the box will add the workflow name as the title of workflow
+         section of the editing form.'
+      ),
+    );
+    $element['widget']['schedule'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Allow scheduling of workflow transitions.'),
+      '#required' => FALSE,
+      '#default_value' => $settings['widget']['schedule'],
+      '#description' => t(
+        'Workflow transitions may be scheduled to a moment in the future.
+         Soon after the desired moment, the transition is executed by Cron.
+         This may be hidden by settings in widgets, formatters or permissions.'
+      ),
+    );
+    $element['widget']['schedule_timezone'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Show a timezone when scheduling a transition.'),
+      '#required' => FALSE,
+      '#default_value' => $settings['widget']['schedule_timezone'],
+    );
+    $element['widget']['comment'] = array(
+      '#type' => 'select',
+      '#title' => t('Allow adding a comment to workflow transitions'),
+      '#required' => FALSE,
+      '#options' => array(
+        // Use 0/1/2 to stay compatible with previous checkbox.
+        0 => t('hidden'),
+        1 => t('optional'),
+        2 => t('required'),
+      ),
+      '#default_value' => $settings['widget']['comment'],
+      '#description' => t('On the Workflow form, a Comment form can be included
+        so that the person making the state change can record reasons for doing
+        so. The comment is then included in the node\'s workflow history. This
+        may be altered by settings in widgets, formatters or permissions.'
+      ),
+    );
+
+    $element['watchdog_log'] = array(
+      '#type' => 'checkbox',
+      '#attributes' => array('class' => array('container-inline')),
+      '#title' => t('Log informational watchdog messages when a transition is
+        executed (a state value is changed)'),
+      '#default_value' => $settings['watchdog_log'],
+      '#description' => t('Optionally log transition state changes to watchdog.'),
+    );
+
+    $element['history'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Workflow history'),
+      '#collapsible' => TRUE,
+      '#collapsed' => FALSE,
+    );
+    $element['history']['history_tab_show'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Use the workflow history, and show it on a separate tab.'),
+      '#required' => FALSE,
+      '#default_value' => $settings['history']['history_tab_show'],
+      '#description' => t("Every state change is recorded in table
+        {workflow_node_history}. If checked and user has proper permission, a
+        tab 'Workflow' is shown on the entity view page, which gives access to
+        the History of the workflow. If you have multiple workflows per bundle,
+        better disable this feature, and use, clone & adapt the Views display
+        'Workflow history per Entity'."),
+    );
+    $element['history']['roles'] = array(
+      '#type' => 'checkboxes',
+      '#options' => workflow_get_roles(),
+      '#title' => t('Workflow history permissions'),
+      '#default_value' => $settings['history']['roles'],
+      '#description' => t('Select any roles that should have access to the workflow tab on nodes that have a workflow.'),
+    );
+
+    return $element;
+  }
+
+  /**
+   * Implements hook_field_insert() -> FieldItemInterface::insert().
+   */
+  public function insert() {
+    return $this->update();
+  }
+
+  /**
+   * Helper functions for the Field Settings page.
+   *
+   * Generates a string representation of an array of 'allowed values'.
+   * This is a copy from list.module's list_allowed_values_string().
+   * The string format is suitable for edition in a textarea.
+   *
+   * @param int $wid
+   *   The Workflow Id.
+   *
+   * @return string
+   * The string representation of the $values array:
+   * - Values are separated by a carriage return.
+   * - Each value is in the format "value|label" or "value".
+   */
+  protected function _allowed_values_string($wid = 0) {
+    $lines = array();
+    $states = workflow_state_load_multiple($wid);
+    $previous_wid = -1;
+
+    /* @var $state WorkflowState */
+    foreach ($states as $state) {
+      // Only show enabled states.
+      if ($state->isActive()) {
+        // Show a Workflow name between Workflows, if more then 1 in the list.
+        if (($wid == 0) && ($previous_wid <> $state->wid)) {
+          $previous_wid = $state->wid;
+          $lines[] = $state->name . "'s states: ";
+        }
+        $label = check_plain(t($state->label()));
+        $states[$state->sid] = $label;
+        $lines[] = $state->sid . ' | ' . $label;
+      }
+    }
+    return implode("\n", $lines);
+  }
+
+  /**
+   * Helper function for list.module formatter.
+   *
+   * Callback function for the list module formatter.
+   *
+   * @see list_allowed_values
+   *   "The strings are not safe for output. Keys and values of the array should
+   *   "be sanitized through field_filter_xss() before being displayed.
+   *
+   * @return array
+   *   The array of allowed values. Keys of the array are the raw stored values
+   *   (number or text), values of the array are the display labels.
+   *   It contains all possible values, beause the result is cached,
+   *   and used for all nodes on a page.
+   */
+  public function getAllowedValues() {
+    // Get all state names, including inactive states.
+    $options = workflow_get_workflow_state_names(0, $grouped = FALSE, $all = TRUE);
+    return $options;
+  }
+
+}

+ 752 - 0
sites/all/modules/contrib/admin/workflow/includes/Form/WorkflowTransitionForm.php

@@ -0,0 +1,752 @@
+<?php
+
+/**
+ * @file
+ * Contains \workflow\Form\WorkflowTransitionForm.
+ */
+
+/**
+ * Provides a Transition Form to be used in the Workflow Widget.
+ */
+class WorkflowTransitionForm { // extends FormBase {
+
+  /**
+   * The Workflow Transition storage.
+   */
+  protected $field;
+  protected $instance;
+  protected $entity;
+
+  /**
+   * Constructs a WorkflowTransitionForm object.
+   * @param array $field
+   * @param array $instance
+   * @param $entity_type
+   * @param $entity
+   */
+  public function __construct(array $field, array $instance, $entity_type, $entity) {
+    $this->field = $field; // TODO : needed?
+    $this->instance = $instance;  // TODO : needed?
+    $this->entity = $entity;  // TODO : needed?
+    $this->entity_type = $entity_type;  // TODO : needed?
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    $field = $this->field;
+    // No entity may be set on VBO form.
+    $entity_id = ($this->entity) ? entity_id($this->entity_type, $this->entity) : '';
+    // The field is not set when editing a stand alone Transition.
+    $field_id = isset($field['id']) ? $field['id'] : '';
+
+    return implode('_', array('workflow_transition_form', $this->entity_type, $entity_id, $field_id));
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @param array $form
+   * @param array $form_state
+   * @param WorkflowTransition
+   *  The Transition to be edited, created.
+   *
+   * @return
+   *  The enhanced form structure.
+   */
+  public function buildForm(array $form, array &$form_state) {
+    global $user;
+
+    /* @var $transition WorkflowTransition */
+    $transition = NULL;
+    if (isset($form_state['WorkflowTransition'])) {
+      // If provided, get data from WorkflowTransition.
+      // This happens when calling entity_ui_get_form(), like in the
+      // WorkflowTransition Comment Edit form.
+      $transition = $form_state['WorkflowTransition'];
+
+      $field_name = $transition->field_name;
+      $workflow = $transition->getWorkflow();
+      $wid = $transition->wid;
+
+      $entity = $this->entity = $transition->getEntity();
+      $entity_type = $this->entity_type = $transition->entity_type;
+      // Figure out the $entity's bundle and id.
+      list(, , $entity_bundle) = entity_extract_ids($entity_type, $entity);
+      $entity_id = entity_id($entity_type, $entity);
+
+      // Show the current state and the Workflow form to allow state changing.
+      // N.B. This part is replicated in hook_node_view, workflow_tab_page, workflow_vbo, transition_edit.
+      // @todo: support multiple workflows per entity.
+      // For workflow_tab_page with multiple workflows, use a separate view. See [#2217291].
+      $field = _workflow_info_field($field_name, $workflow);
+      $instance = $this->instance + field_info_instance($entity_type, $field_name, $entity_bundle);
+    }
+    else {
+      // Get data from normal parameters.
+      $entity = $this->entity;
+      $entity_type = $this->entity_type;
+      $entity_id = ($entity) ? entity_id($entity_type, $entity) : 0;
+
+      $field = $this->field;
+      $field_name = $field['field_name'];
+      $instance = $this->instance;
+
+      // $field['settings']['wid'] can be numeric or named.
+      // $wid may not be specified.
+      $wid = $field['settings']['wid'];
+      $workflow = workflow_load_single($wid);
+    }
+
+    $force = FALSE;
+
+    // Get values.
+    // Current sid and default value may differ in a scheduled transition.
+    // Set 'grouped' option. Only valid for select list and undefined/multiple workflows.
+    $settings_options_type = $field['settings']['widget']['options'];
+    $grouped = ($settings_options_type == 'select');
+    if ($transition) {
+      // If a Transition is passed as parameter, use this.
+      if ($transition->isExecuted()) {
+        // We are editing an existing/executed/not-scheduled transition.
+        // Only the comments may be changed!
+        // Fetch the old state for the formatter on top of form.
+        $current_state = $transition->getOldState();
+        $current_sid = $current_state->sid;
+
+        // The states may not be changed anymore.
+        $new_state = $transition->getNewState();
+        $options = array($new_state->sid => $new_state->label());
+        // We need the widget to edit the comment.
+        $show_widget = TRUE;
+      }
+      else {
+        $current_state = $transition->getOldState();
+        $current_sid = $current_state->sid;
+        $options = $current_state->getOptions($entity_type, $entity, $field_name, $user, $force);
+        $show_widget = $current_state->showWidget($entity_type, $entity, $field_name, $user, $force);
+      }
+      $default_value = $transition->new_sid;
+    }
+    elseif (!$entity) {
+      // Sometimes, no entity is given. We encountered the following cases:
+      // - the Field settings page,
+      // - the VBO action form;
+      // - the Advance Action form on admin/config/system/actions;
+      // If so, show all options for the given workflow(s).
+      $options = workflow_get_workflow_state_names($wid, $grouped, $all = FALSE);
+      $show_widget = TRUE;
+      $default_value = $current_sid = isset($items[0]['value']) ? $items[0]['value'] : '0';
+    }
+    else {
+      $current_sid = workflow_node_current_state($entity, $entity_type, $field_name);
+      if ($current_state = workflow_state_load_single($current_sid)) {
+        /* @var $current_state WorkflowTransition */
+        $options = $current_state->getOptions($entity_type, $entity, $field_name, $user, $force);
+        $show_widget = $current_state->showWidget($entity_type, $entity, $field_name, $user, $force);
+        $default_value = !$current_state->isCreationState() ? $current_sid : $workflow->getFirstSid($entity_type, $entity, $field_name, $user, FALSE);
+      }
+      else {
+        // We are in trouble! A message is already set in workflow_node_current_state().
+        $options = array();
+        $show_widget = FALSE;
+        $default_value = $current_sid;
+      }
+
+      // Get the scheduling info. This may change the $default_value on the Form.
+      // Read scheduled information, only if an entity exists.
+      // Technically you could have more than one scheduled, but this will only add the soonest one.
+      foreach (WorkflowScheduledTransition::load($entity_type, $entity_id, $field_name, 1) as $transition) {
+        $default_value = $transition->new_sid;
+        break;
+      }
+    }
+    // Prepare a new transition, if still not provided.
+    if (!$transition) {
+      $transition = new WorkflowTransition(array(
+        'old_sid' => $default_value,
+        'stamp' => REQUEST_TIME,
+      ));
+    }
+
+    // Fetch the form ID. This is unique for each entity, to allow multiple form per page (Views, etc.).
+    // Make it uniquer by adding the field name, or else the scheduling of
+    // multiple workflow_fields is not independent of each other.
+    // IF we are truly on a Transition form (so, not a Node Form with widget)
+    // then change the form id, too.
+    $form_id = $this->getFormId();
+    if (!isset($form_state['build_info']['base_form_id'])) {
+      // Strange: on node form, the base_form_id is node_form,
+      // but on term form, it is not set.
+      // In both cases, it is OK. 
+    }
+    else {
+      if ($form_state['build_info']['base_form_id'] == 'workflow_transition_wrapper_form') {
+        $form_state['build_info']['base_form_id'] = 'workflow_transition_form';
+      }
+      if ($form_state['build_info']['base_form_id'] == 'workflow_transition_form') {
+        $form_state['build_info']['form_id'] = $form_id;
+      }
+    }
+
+    $workflow_label = $workflow ? check_plain(t($workflow->label())) : '';
+
+    // Change settings locally.
+    if (!$field_name) {
+      // This is a Workflow Node workflow. Set widget options as in v7.x-1.2
+      if ($form_state['build_info']['base_form_id'] == 'node_form') {
+        $field['settings']['widget']['comment'] = isset($workflow->options['comment_log_node']) ? $workflow->options['comment_log_node'] : 1; // vs. ['comment_log_tab'];
+        $field['settings']['widget']['current_status'] = TRUE;
+      }
+      else {
+        $field['settings']['widget']['comment'] = isset($workflow->options['comment_log_tab']) ? $workflow->options['comment_log_tab'] : 1; // vs. ['comment_log_node'];
+        $field['settings']['widget']['current_status'] = TRUE;
+      }
+    }
+
+    // Capture settings to format the form/widget.
+    $settings_title_as_name = !empty($field['settings']['widget']['name_as_title']);
+    $settings_fieldset = isset($field['settings']['widget']['fieldset']) ? $field['settings']['widget']['fieldset'] : 0;
+    $settings_options_type = $field['settings']['widget']['options'];
+    // The scheduling info can be hidden via field settings, ...
+    // You may not schedule an existing Transition.
+    // You must have the correct permission.
+    $settings_schedule = !empty($field['settings']['widget']['schedule']) && !$transition->isExecuted() && user_access('schedule workflow transitions');
+    if ($settings_schedule) {
+      if (isset($form_state['step']) && ($form_state['step'] == 'views_bulk_operations_config_form')) {
+        // On VBO 'modify entity values' form, leave field settings.
+        $settings_schedule = TRUE;
+      }
+      else {
+        // ... and cannot be shown on a Content add page (no $entity_id),
+        // ...but can be shown on a VBO 'set workflow state to..'page (no entity).
+        $settings_schedule = !($entity && !$entity_id);
+      }
+    }
+    $settings_schedule_timezone = !empty($field['settings']['widget']['schedule_timezone']);
+    // Show comment, when both Field and Instance allow this.
+    $settings_comment = $field['settings']['widget']['comment'];
+
+    // Save the current value of the node in the form, for later Workflow-module specific references.
+    // We add prefix, since #tree == FALSE.
+    $element['workflow']['workflow_entity'] = array(
+      '#type' => 'value',
+      '#value' => $this->entity,
+    );
+    $element['workflow']['workflow_entity_type'] = array(
+      '#type' => 'value',
+      '#value' => $this->entity_type,
+    );
+    $element['workflow']['workflow_field'] = array(
+      '#type' => 'value',
+      '#value' => $field,
+    );
+    $element['workflow']['workflow_instance'] = array(
+      '#type' => 'value',
+      '#value' => $instance,
+    );
+
+    // Save the form_id, so the form values can be retrieved in submit function.
+    $element['workflow']['form_id'] = array(
+      '#type' => 'value',
+      '#value' => $form_id,
+    );
+
+    // Save the hid, when editing an existing transition.
+    $element['workflow']['workflow_hid'] = array(
+      '#type' => 'hidden',
+      '#value' => $transition->hid,
+    );
+
+    // Add the default value in the place where normal fields
+    // have it. This is to cater for 'preview' of the entity.
+    $element['#default_value'] = $default_value;
+
+    // Decide if we show a widget or a formatter.
+    // There is no need for a widget when the only option is the current sid.
+
+    // Show state formatter before the rest of the form,
+    // when transition is scheduled or widget is hidden.
+    if ( (!$show_widget) || $transition->isScheduled() || $transition->isExecuted()) {
+      $form['workflow_current_state'] = workflow_state_formatter($entity_type, $entity, $field, $instance, $current_sid);
+      // Set a proper weight, which works for Workflow Options in select list AND action buttons.
+      $form['workflow_current_state']['#weight'] = -0.005;
+    }
+
+    // Add class following node-form pattern (both on form and container).
+    $workflow_type_id = ($workflow) ? $workflow->getName() : 'none'; // No workflow on New Action form.
+    $element['workflow']['#attributes']['class'][] = 'workflow-transition-container';
+    $element['workflow']['#attributes']['class'][] = 'workflow-transition-' . $workflow_type_id . '-container';
+    // Add class for D7-backwards compatibility (only on container).
+    $element['workflow']['#attributes']['class'][] = 'workflow-form-container';
+
+    if (!$show_widget) {
+      // Show no widget.
+      $element['workflow']['workflow_sid']['#type'] = 'value';
+      $element['workflow']['workflow_sid']['#value'] = $default_value;
+      $element['workflow']['workflow_sid']['#options'] = $options; // In case action buttons need them.
+
+      $form += $element;
+      return $form;  // <-- exit.
+    }
+    else {
+      // Prepare a UI wrapper. This might be a fieldset or a container.
+      if ($settings_fieldset == 0) { // Use 'container'.
+        $element['workflow'] += array(
+          '#type' => 'container',
+        );
+      }
+      else {
+        $element['workflow'] += array(
+          '#type' => 'fieldset',
+          '#title' => t($workflow_label),
+          '#collapsible' => TRUE,
+          '#collapsed' => ($settings_fieldset == 1) ? FALSE : TRUE,
+        );
+      }
+
+      // The 'options' widget. May be removed later if 'Action buttons' are chosen.
+      // The help text is not available for container. Let's add it to the
+      // State box.
+      $help_text = isset($instance['description']) ? $instance['description'] : '';
+      $element['workflow']['workflow_sid'] = array(
+        '#type' => $settings_options_type,
+        '#title' => $settings_title_as_name ? t('Change !name state', array('!name' => $workflow_label)) : t('Target state'),
+        '#access' => TRUE,
+        '#options' => $options,
+        // '#name' => $workflow_label,
+        // '#parents' => array('workflow'),
+        '#default_value' => $default_value,
+        '#description' => $help_text,
+      );
+    }
+
+    // Display scheduling form, but only if entity is being edited and user has
+    // permission. State change cannot be scheduled at entity creation because
+    // that leaves the entity in the (creation) state.
+    if ($settings_schedule == TRUE) {
+      if (variable_get('configurable_timezones', 1) && $user->uid && drupal_strlen($user->timezone)) {
+        $timezone = $user->timezone;
+      }
+      else {
+        $timezone = variable_get('date_default_timezone', 0);
+      }
+      $timezones = drupal_map_assoc(timezone_identifiers_list());
+      $timestamp = $transition->getTimestamp();
+      $hours = (!$transition->isScheduled()) ? '00:00' : format_date($timestamp, 'custom', 'H:i', $timezone);
+      // Add a container, so checkbox and time stay together in extra fields.
+      $element['workflow']['workflow_scheduling'] = array(
+        '#type' => 'container',
+        '#tree' => TRUE,
+      );
+      $element['workflow']['workflow_scheduling']['scheduled'] = array(
+        '#type' => 'radios',
+        '#title' => t('Schedule'),
+        '#options' => array(
+          '0' => t('Immediately'),
+          '1' => t('Schedule for state change'),
+        ),
+        '#default_value' => $transition->isScheduled() ? '1' : '0',
+        '#attributes' => array(
+          // 'id' => 'scheduled_' . $form_id,
+          'class' => array(drupal_html_class('scheduled_' .  $form_id)),
+        ),
+      );
+      $element['workflow']['workflow_scheduling']['date_time'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('At'),
+        '#attributes' => array('class' => array('container-inline')),
+        '#prefix' => '<div style="margin-left: 1em;">',
+        '#suffix' => '</div>',
+        '#states' => array(
+          //'visible' => array(':input[id="' . 'scheduled_' . $form_id . '"]' => array('value' => '1')),
+          'visible' => array('input.' . drupal_html_class('scheduled_' .  $form_id) => array('value' => '1')),
+        ),
+      );
+      $element['workflow']['workflow_scheduling']['date_time']['workflow_scheduled_date'] = array(
+        '#type' => 'date',
+        '#default_value' => array(
+          'day' => date('j', $timestamp),
+          'month' => date('n', $timestamp),
+          'year' => date('Y', $timestamp),
+        ),
+      );
+      $element['workflow']['workflow_scheduling']['date_time']['workflow_scheduled_hour'] = array(
+        '#type' => 'textfield',
+        '#title' => t('Time'),
+        '#maxlength' => 7,
+        '#size' => 6,
+        '#default_value' => $hours,
+        '#element_validate' => array('_workflow_transition_form_element_validate_time'),
+      );
+      $element['workflow']['workflow_scheduling']['date_time']['workflow_scheduled_timezone'] = array(
+        '#type' => $settings_schedule_timezone ? 'select' : 'hidden',
+        '#title' => t('Time zone'),
+        '#options' => $timezones,
+        '#default_value' => array($timezone => $timezone),
+      );
+      $element['workflow']['workflow_scheduling']['date_time']['workflow_scheduled_help'] = array(
+        '#type' => 'item',
+        '#prefix' => '<br />',
+        '#description' => t('Please enter a time.
+          If no time is included, the default will be midnight on the specified date.
+          The current time is: @time.', array('@time' => format_date(REQUEST_TIME, 'custom', 'H:i', $timezone))
+        ),
+      );
+    }
+
+    $element['workflow']['workflow_comment'] = array(
+      '#type' => 'textarea',
+      '#required' => $settings_comment == '2',
+      '#access' => $settings_comment !='0', // Align with action buttons.
+      '#title' => t('Workflow comment'),
+      '#description' => t('A comment to put in the workflow log.'),
+      '#default_value' => $transition->comment,
+      '#rows' => 2,
+    );
+
+    // Add the fields and extra_fields from the WorkflowTransition.
+    // Because we have a 'workflow' wrapper, it doesn't work flawlessly.
+    field_attach_form('WorkflowTransition', $transition, $element['workflow'], $form_state);
+    // Undo the following elements from field_attach_from. They mess up $this->getTransition().
+    // - '#parents' corrupts the Defaultwidget.
+    unset($element['workflow']['#parents']);
+    // - '#pre_render' adds the exra_fields from workflow_field_extra_fields().
+    //   That doesn't work, since 'workflow' is not of #type 'form', but
+    //   'container' or 'fieldset', and must be executed separately,.
+    $element['workflow']['#pre_render'] = array_diff( $element['workflow']['#pre_render'], array('_field_extra_fields_pre_render') );
+    // Add extra fields.
+    $rescue_value = $element['workflow']['#type'];
+    $element['workflow']['#type'] = 'form';
+    $element['workflow'] = _field_extra_fields_pre_render($element['workflow']);
+    $element['workflow']['#type'] = $rescue_value;
+
+    // Finally, add Submit buttons/Action buttons.
+    // Either a default 'Submit' button is added, or a button per permitted state.
+    if ($settings_options_type == 'buttons') {
+      // How do action buttons work? See also d.o. issue #2187151.
+      // Create 'action buttons' per state option. Set $sid property on each button.
+      // 1. Admin sets ['widget']['options']['#type'] = 'buttons'.
+      // 2. This function formElement() creates 'action buttons' per state option;
+      //    sets $sid property on each button.
+      // 3. User clicks button.
+      // 4. Callback _workflow_transition_form_validate_buttons() sets proper State.
+      // 5. Callback _workflow_transition_form_validate_buttons() sets Submit function.
+      // @todo: this does not work yet for the Add Comment form.
+
+      // Performance: inform workflow_form_alter() to do its job.
+      _workflow_use_action_buttons(TRUE);
+
+      // Hide the options box. It will be replaced by action buttons.
+      $element['workflow']['workflow_sid']['#type'] = 'select';
+      $element['workflow']['workflow_sid']['#access'] = FALSE;
+    }
+
+    // Some forms (Term) do not have 'base_form_id' set.
+    if (isset($form_state['build_info']['base_form_id']) && $form_state['build_info']['base_form_id'] == 'workflow_transition_form') {
+      // Add action buttons on WorkflowTransitionForm (history tab, formatter)
+      // but not on Entity form, and not if action_buttons is selected.
+
+      // you can explicitly NOT add a submit button, e.g., on VBO page.
+      if ($instance['widget']['settings']['submit_function'] !== '') {
+        // @todo D8: put buttons outside of 'workflow' element, in the standard location.
+        $element['workflow']['actions']['#type'] = 'actions';
+        $element['workflow']['actions']['submit'] = array(
+          '#type' => 'submit',
+//        '#access' => TRUE,
+          '#value' => t('Update workflow'),
+          '#weight' => -5,
+//        '#submit' => array( isset($instance['widget']['settings']['submit_function']) ? $instance['widget']['settings']['submit_function'] : NULL),
+          // '#executes_submit_callback' => TRUE,
+          '#attributes' => array('class' => array('form-save-default-button')),
+        );
+        // The 'add submit' can explicitly set by workflowfield_field_formatter_view(),
+        // to add the submit button on the Content view page and the Workflow history tab.
+        // Add a submit button, but only on Entity View and History page.
+        // Add the submit function only if one provided. Set the submit_callback accordingly.
+        if (!empty($instance['widget']['settings']['submit_function'])) {
+          $element['workflow']['actions']['submit']['#submit'] = array($instance['widget']['settings']['submit_function']);
+        }
+        else {
+          // '#submit' Must be empty, or else the submit function is not called.
+          // $element['workflow']['actions']['submit']['#submit'] = array();
+        }
+      }
+    }
+    /*
+    $submit_functions = empty($instance['widget']['settings']['submit_function']) ? array() : array($instance['widget']['settings']['submit_function']);
+    if ($settings_options_type == 'buttons' || $submit_functions) {
+    }
+    else {
+      // In some cases, no submit callback function is specified. This is
+      // explicitly done on e.g., the node edit form, because the workflow form
+      // is 'just a field'.
+      // So, no Submit button is to be shown.
+    }
+     */
+
+    $form += $element;
+
+    // Add class following node-form pattern (both on form and container).
+    $workflow_type_id = ($workflow) ? $workflow->getName() : 'none'; // No workflow on New Action form.
+    $form['#attributes']['class'][] = 'workflow-transition-form';
+    $form['#attributes']['class'][] = 'workflow-transition-' . $workflow_type_id . '-form';
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, array $form_state) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, array &$form_state, array &$items) {
+    // $items is a D7 parameter.
+    // @todo: clean this code up. It is the result of gluing code together.
+    global $user; // @todo #2287057: verify if submit() really is only used for UI. If not, $user must be passed.
+
+    $entity = $this->entity;
+    $entity_type = $this->entity_type;
+
+    $field = $this->field;
+    $field_name = $field['field_name'];
+
+    // Retrieve the data from the form.
+    if (isset($form_state['values']['workflow_field'])) {
+      // If $entity filled: We are on a Entity View page or Workflow History Tab page.
+      // If $entity empty: We are on an Advanced Action page.
+//      $field = $form_state['values']['workflow_field'];
+//      $instance = $form_state['values']['workflow_instance'];
+//      $entity_type = $form_state['values']['workflow_entity_type'];
+//      $entity = $form_state['values']['workflow_entity'];
+//      $field_name = $field['field_name'];
+    }
+    elseif (isset($form_state['triggering_element'])) {
+      // We are on an Entity/Node/Comment Form page (add/edit).
+      $field_name = $form_state['triggering_element']['#workflow_field_name'];
+    }
+    else {
+      // We are on an Entity/Comment Form page (add/edit).
+    }
+
+    // Determine if the transition is forced.
+    // This can be set by a 'workflow_vbo action' in an additional form element.
+    $force = isset($form_state['input']['workflow_force']) ? $form_state['input']['workflow_force'] : FALSE;
+
+    // Set language. Multi-language is not supported for Workflow Node.
+    $langcode = _workflow_metadata_workflow_get_properties($entity, array(), 'langcode', $entity_type, $field_name);
+
+    if (!$entity) {
+      // E.g., on VBO form.
+    }
+    elseif ($field_name) {
+      // Save the entity, but only if we were not in edit mode.
+      // Perhaps there is a better way, but for now we use 'changed' property.
+      // Also test for 'is_new'. When Migrating content, the 'changed' property may be set externally.
+      // Caveat: Some entities do not have 'changed' property set.
+      if ((!empty($entity->is_new)) || (isset($entity->changed) && $entity->changed == REQUEST_TIME)) {
+        // N.B. ONLY for Nodes!
+        // We are in add/edit mode. No need to save the entity explicitly.
+
+//        // Add the $form_state to the $items, so we can do a getTransition() later on.
+//        $items[0]['workflow'] = $form_state['input'];
+//        // Create a Transition. The Widget knows if it is scheduled.
+//        $widget = new WorkflowDefaultWidget($field, $instance, $entity_type, $entity);
+//        $new_sid = $widget->submit($form, $form_state, $items, $force);
+      }
+      elseif (isset($form_state['input'])) {
+        // Save $entity, but only if sid has changed.
+        // Use field_attach_update for this? Save always?
+        $entity->{$field_name}[$langcode][0]['workflow'] = $form_state['input'];
+        // @todo & totest: Save ony the field, not the complete entity.
+        // workflow_entity_field_save($entity_type, $entity, $field_name, $langcode, FALSE);
+        entity_save($entity_type, $entity);
+
+        return; // <-- exit!
+      }
+      elseif ($entity_type == 'node') {
+        // N.B. ONLY for Nodes!
+        // We are saving a node from a comment.
+        $entity->{$field_name}[$langcode] = $items;
+        // @todo & totest: Save ony the field, not the complete entity.
+        // workflow_entity_field_save($entity_type, $entity, $field_name, $langcode, FALSE);
+        entity_save($entity_type, $entity);
+
+        return; // <-- exit!
+      }
+      else {
+        // We are saving a non-node from an entity form.
+        $entity->{$field_name}[$langcode] = $items;
+      }
+    }
+    else {
+      // For a Node API form, only contrib fields need to be filled.
+      // No updating of the node itself.
+      // (Unless we need to record the timestamp.)
+
+      // Add the $form_state to the $items, so we can do a getTransition() later on.
+      $items[0]['workflow'] = $form_state['input'];
+//      // Create a Transition. The Widget knows if it is scheduled.
+//      $widget = new WorkflowDefaultWidget($field, $instance, $entity_type, $entity);
+//      $new_sid = $widget->submit($form, $form_state, $items, $force);
+    }
+
+    // Extract the data from $items, depending on the type of widget.
+    // @todo D8: use MassageFormValues($values, $form, $form_state).
+    $old_sid = workflow_node_previous_state($entity, $entity_type, $field_name);
+    if (!$old_sid) {
+      // At this moment, $old_sid should have a value. If the content does not
+      // have a state yet, old_sid contains '(creation)' state. But if the
+      // content is not associated to a workflow, old_sid is now 0. This may
+      // happen in workflow_vbo, if you assign a state to non-relevant nodes.
+      $entity_id = entity_id($entity_type, $entity);
+      drupal_set_message(t('Error: content !id has no workflow attached. The data is not saved.', array('!id' => $entity_id)), 'error');
+      // The new state is still the previous state.
+      $new_sid = $old_sid;
+      return $new_sid;
+    }
+
+    // Now, save/execute the transition.
+    $transition = $this->getTransition($old_sid, $items, $field_name, $user, $form, $form_state);
+
+    // Try to execute the transition. Return $old_sid when error.
+    if (!$transition) {
+      // This should only happen when testing/developing.
+      drupal_set_message(t('Error: the transition from %old_sid to %new_sid could not be generated.'), 'error');
+      // The current value is still the previous state.
+      $new_sid = $old_sid;
+    }
+    elseif ($transition->isScheduled() || $transition->isExecuted()) {
+      // A scheduled or executed transition must only be saved to the database.
+      // The entity is not changed.
+      $force = $force || $transition->isForced();
+      $transition->save();
+
+      // The current value is still the previous state.
+      $new_sid = $old_sid;
+    }
+    elseif (!$transition->isScheduled()) {
+      // Now the data is captured in the Transition, and before calling the
+      // Execution, restore the default values for Workflow Field.
+      // For instance, workflow_rules evaluates this.
+      if ($field_name) {
+        // $items = array();
+        // $items[0]['value'] = $old_sid;
+        // $entity->{$field_name}[$transition->language] = $items;
+      }
+
+      // It's an immediate change. Do the transition.
+      // - validate option; add hook to let other modules change comment.
+      // - add to history; add to watchdog
+      // Return the new State ID. (Execution may fail and return the old Sid.)
+      $force = $force || $transition->isForced();
+      $new_sid = $transition->execute($force);
+    }
+
+    // The entity is still to be saved, so set to a 'normal' value.
+    if ($field_name) {
+      $items = array();
+      $items[0]['value'] = $new_sid;
+      $entity->{$field_name}[$transition->language] = $items;
+    }
+
+    return $new_sid;
+  }
+
+  /**
+   * Extract WorkflowTransition or WorkflowScheduledTransition from the form.
+   *
+   * This merely extracts the transition from the form/widget. No validation.
+   *
+   * @param $old_sid
+   * @param array $items
+   * @param $field_name
+   * @param \stdClass $user
+   *
+   * @return \WorkflowScheduledTransition|\WorkflowTransition|null
+   */
+  public function getTransition($old_sid, array $items, $field_name, stdClass $user, array &$form = array(), array &$form_state = array()) {
+    $entity_type = $this->entity_type;
+    $entity = $this->entity;
+    // $entity_id = entity_id($entity_type, $entity);
+    $field_name = !empty($this->field) ? $this->field['field_name'] : '';
+
+    if (isset($items[0]['transition'])) {
+      // a complete transition was already passed on.
+      $transition = $items[0]['transition'];
+    }
+    else {
+      // Get the new Transition properties. First the new State ID.
+      if (isset($items[0]['workflow']['workflow_sid'])) {
+        // We have shown a workflow form.
+        $new_sid = $items[0]['workflow']['workflow_sid'];
+      }
+      elseif (isset($items[0]['value'])) {
+        // We have shown a core options widget (radios, select).
+        $new_sid = $items[0]['value'];
+      }
+      else {
+        // This may happen if only 1 option is left, and a formatter is shown.
+        $state = workflow_state_load_single($old_sid);
+        if (!$state->isCreationState()) {
+          $new_sid = $old_sid;
+        }
+        else {
+          // This only happens on workflows, when only one transition from
+          // '(creation)' to another state is allowed.
+          /* @var $workflow Workflow */
+          $workflow = $state->getWorkflow();
+          $new_sid = $workflow->getFirstSid($this->entity_type, $this->entity, $field_name, $user, FALSE);
+        }
+      }
+      // If an existing Transition has been edited, $hid is set.
+      $hid = isset($items[0]['workflow']['workflow_hid']) ? $items[0]['workflow']['workflow_hid'] : '';
+      // Get the comment.
+      $comment = isset($items[0]['workflow']['workflow_comment']) ? $items[0]['workflow']['workflow_comment'] : '';
+      // Remember, the workflow_scheduled element is not set on 'add' page.
+      $scheduled = !empty($items[0]['workflow']['workflow_scheduling']['scheduled']);
+      if ($hid) {
+        // We are editing an existing transition. Only comment may be changed.
+        $transition = workflow_transition_load($hid);
+        $transition->comment = $comment;
+      }
+      elseif (!$scheduled) {
+        $transition = new WorkflowTransition();
+        $transition->setValues($entity_type, $entity, $field_name, $old_sid, $new_sid, $user->uid, REQUEST_TIME, $comment);
+      }
+      else {
+        // Schedule the time to change the state.
+        // If Field Form is used, use plain values;
+        // If Node Form is used, use fieldset 'date_time'.
+        $schedule = isset($items[0]['workflow']['workflow_scheduling']['date_time']) ? $items[0]['workflow']['workflow_scheduling']['date_time'] : $items[0]['workflow'];
+        if (!isset($schedule['workflow_scheduled_hour'])) {
+          $schedule['workflow_scheduled_hour'] = '00:00';
+        }
+
+        $scheduled_date_time
+          = $schedule['workflow_scheduled_date']['year']
+          . substr('0' . $schedule['workflow_scheduled_date']['month'], -2, 2)
+          . substr('0' . $schedule['workflow_scheduled_date']['day'], -2, 2)
+          . ' '
+          . $schedule['workflow_scheduled_hour']
+          . ' '
+          . $schedule['workflow_scheduled_timezone'];
+
+        if ($timestamp = strtotime($scheduled_date_time)) {
+          $transition = new WorkflowScheduledTransition();
+          $transition->setValues($entity_type, $entity, $field_name, $old_sid, $new_sid, $user->uid, $timestamp, $comment);
+        }
+        else {
+          $transition = NULL;
+        }
+      }
+    }
+    return $transition;
+  }
+
+}

+ 84 - 0
sites/all/modules/contrib/admin/workflow/includes/Test/WorkflowUnitTest.test

@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * @file
+ * Contains workflow\lib\entity\WorkflowUnitTest.
+ */
+
+/**
+ * Tests for the Workflow classes.
+ */
+class WorkflowExampleTestCase extends DrupalWebTestCase {
+  protected $workflow;
+  public static function getInfo() {
+    return array(
+      'name' => 'Workflow',
+      'description' => 'Ensure that the Workflow API works as expected.',
+      'group' => 'Workflow',
+    );
+  }
+
+  public function setUp() {
+    parent::setUp('workflow');  // Enable any modules required for the test.
+  }
+
+  /**
+    * Creates a simpletest_example node using the node form.
+    */
+  public function testWorkflow() {
+
+//    $workflows = workflow_load_multiple();
+//    dpm($workflows, "These are the current Workflows in the system.");
+    $wid = 1;
+    $workflow = workflow_load_single($wid);
+    $this->assertEqual($workflow->wid, $wid, t('The wid of the Workflow should be the same as we decided.'));
+    $workflow = new Workflow($wid);
+    $this->assertEqual($workflow->wid, $wid, t('The wid of the Workflow should be the same as we decided.'));
+
+//    $creation_state = $workflow->getCreationState();
+//    $this->assertEqual($creation_state, 1, t('The creation_state of wid 1 has value 1.'));
+
+  }
+
+/*
+  function testWorkflowState($sid = 2, $wid = 1) {
+    $this->testId = '2';
+
+    $wf_state =  $workflow->createState('State 2');
+dpm($wf_state, 'This is state ' . $sid . ' of workflow ' . $wid);
+    $workflow = $wf_state->getWorkflow();
+dpm($workflow, 'This is the workflow of sid ' . $sid);
+
+
+return;
+    $this->machine->fire_event('goto2');
+    $this->assertEqual($this->machine->get_current_state(), 'step2', t('Current state should change when a valid event is fired.'));
+
+    $this->machine->fire_event('goto2');
+    $this->assertEqual($this->machine->get_current_state(), 'step2', t('Event should not execute if current state is not valid for the specified event.'));
+
+    $this->machine->fire_event('reset');
+    $this->assertEqual($this->machine->get_current_state(), 'step1', t('Event should allow transitions from multiple origins.'));
+
+    $current = $this->machine->get_current_state();
+    $this->machine->fire_event('dont_do_it');
+    $this->assertEqual($current, $this->machine->get_current_state(), t('State should not change when guard function returns FALSE.'));
+
+    $this->machine->fire_event('reset');
+    $this->machine->reset_logs();
+    $this->machine->fire_event('goto2_with_logs');
+
+    $this->assertEqual($this->machine->logs[0], 'guard', t('The guard condition should be the first callback executed.'));
+    $this->assertEqual($this->machine->logs[1], 'before_transition', t('The before_transition callback should be the second callback executed.'));
+    $this->assertEqual($this->machine->logs[2], 'on_exit', t('The on_exit callback should be the third callback executed.'));
+    $this->assertEqual($this->machine->logs[3], 'on_enter', t('The on_enter callback should be the fourth callback executed.'));
+    $this->assertEqual($this->machine->logs[4], 'after_transition', t('The after_transition callback should be the fifth callback executed.'));
+
+    $this->machine->fire_event('reset');
+    $events = $this->machine->get_available_events();
+    $this->assertTrue(in_array('goto2', $events), t('The machine should return a list of available events.'));
+    $this->assertTrue(in_array('goto3', $events), t('The machine should return a list of available events.'));
+  }
+ */
+
+}

+ 250 - 23
sites/all/modules/contrib/admin/workflow/workflow.api.php

@@ -7,57 +7,284 @@
 /**
  * Implements hook_workflow().
  *
- * @param $op
- *   The current workflow operation: 'transition permitted', 'transition pre' or 'transition post'.
- * @param $old_state
- *   The state ID of the current state.
- * @param $new_state
+ * NOTE: This hook may reside in the implementing module
+ * or in a module.workflow.inc file.
+ *
+ * @param string $op
+ *   The current workflow operation.
+ *   E.g., 'transition permitted', 'transition pre' or 'transition post'.
+ * @param mixed $id
+ *   The ID of the current state/transition/workflow.
+ * @param mixed $new_sid
  *   The state ID of the new state.
- * @param $node
- *   The node whose workflow state is changing.
- * @param $force
+ * @param object $entity
+ *   The entity whose workflow state is changing.
+ * @param bool $force
  *   The caller indicated that the transition should be forced. (bool).
  *   This is only available on the "pre" and "post" calls.
+ * @param string $entity_type
+ *   The entity_type of the entity whose workflow state is changing.
+ * @param string $field_name
+ *   The name of the Workflow Field. Empty in case of Workflow Node.
+ *   This is used when saving a state change of a Workflow Field.
+ * @param object $transition
+ *   The transition, that contains all of the above.
+ *   @todo D8: remove all other parameters.
+ *
+ * @return mixed
+ *   Only 'transition permitted' expects a boolean result.
  */
-function hook_workflow($op, $old_state, $new_state, $node, $force = FALSE) {
+function hook_workflow($op, $id, $new_sid, $entity, $force, $entity_type = '', $field_name = '', $transition = NULL, $user = NULL) {
   switch ($op) {
     case 'transition permitted':
-      // The workflow module does nothing during this operation.
-      // This operation occurs when the list of available transitions
-      // is built. Your module's implementation could return FALSE
-      // here and disallow the presentation of the choice.
-      break;
+      // This is called in the following situations:
+      // 1. when building a workflow widget with list of available transitions;
+      // 2. when executing a transition, just before the 'transition pre';
+      // 3. when showing a 'revert state' link in a Views display.
+      // Your module's implementation may return FALSE here and disallow
+      // the execution, or avoid the presentation of the new State.
+      // This may be user-dependent.
+      // As of 7.x-2.3, better use hook_workflow_permitted_state_transitions_alter() in option 1.
+      // For options 2 and 3, the 'transition pre' gives an alternative.
+      return TRUE;
 
     case 'transition pre':
       // The workflow module does nothing during this operation.
-      // But your module's implementation of the workflow hook could
-      // return FALSE here and veto the transition.
+      // Implement this hook if you need to change/do something BEFORE anything
+      // is saved to the database.
+      // If you return FALSE here, you will veto the transition.
       break;
 
     case 'transition post':
+      // This is called by Workflow Node during update of the state, directly
+      // after updating {workflow_node}. Workflow Field does not call this,
+      // since you can call a hook_entity_* event after saving the entity.
+      // @see https://api.drupal.org/api/drupal/includes%21module.inc/group/hooks/7
       break;
 
     case 'transition delete':
+      // A transition is deleted. Only the first parameter is used.
+      // $tid = $id;
+      break;
+
+    case 'state delete':
+      // A state is deleted. Only the first parameter is used.
+      // $sid = $id;
+      break;
+
+    case 'workflow delete':
+      // A workflow is deleted. Only the first parameter is used.
+      // $wid = $id;
       break;
   }
 }
 
 /**
  * Implements hook_workflow_history_alter().
- * Add an 'undo' operation for the most recent history change.
  *
- * @param $variables
+ * Allow other modules to add Operations to the most recent history change.
+ * E.g., Workflow Revert implements an 'undo' operation.
+ *
+ * @param array $variables
  *   The current workflow history information as an array.
  *   'old_sid' - The state ID of the previous state.
  *   'old_state_name' - The state name of the previous state.
  *   'sid' - The state ID of the current state.
  *   'state_name' - The state name of the current state.
  *   'history' - The row from the workflow_node_history table.
- *
- * If you want to add additional data, such as an operation link,
- * place it in the 'extra' value.
+ *   'transition' - a WorkflowTransition object, containing all of the above.
  */
-function hook_workflow_history_alter(&$variables) {
-  // The Worflow module does nothing with this hook.
+function hook_workflow_history_alter(array &$variables) {
+  // The Workflow module does nothing with this hook.
   // For an example implementation, see the Workflow Revert add-on.
+  $options = array();
+  $path = '<front>';
+
+  // If you want to add additional data, such as an operation link,
+  // place it in the 'extra' value.
+  $variables['extra'] = l(t('My new operation: go to frontpage'), $path, $options);
+}
+
+/**
+ * Implements hook_workflow_comment_alter().
+ *
+ * Allow other modules to change the user comment when saving a state change.
+ *
+ * @param string $comment
+ *   The comment of the current state transition.
+ * @param array $context
+ *   'transition' - The current transition itself.
+ */
+function hook_workflow_comment_alter(&$comment, array &$context) {
+  $transition = $context->transition;
+  $comment = $transition->uid . 'says: ' . $comment;
+}
+
+/**
+ * Implements hook_workflow_permitted_state_transitions_alter().
+ *
+ * @param array $transitions
+ *  An array of allowed transitions from the current state (as provided in
+ *  $context). They are already filtered by the settings in Admin UI.
+ * @param array $context
+ *  An array of relevant objects. Currently:
+ *    $context = array(
+ *      'entity_type' => $entity_type,
+ *      'entity' => $entity,
+ *      'field_name' => $field_name,
+ *      'force' => $force,
+ *      'workflow' => $workflow,
+ *      'state' => $current_state,
+ *      'user' => $user,
+ *      'user_roles' => $roles, // @todo: can be removed in D8, since $user is in.
+ *    );
+ *
+ * This hook allows you to add custom filtering of allowed target states, add
+ * new custom states, change labels, etc.
+ * It is invoked in WorkflowState::getOptions().
+ */
+function hook_workflow_permitted_state_transitions_alter(array &$transitions, array $context) {
+  // This example creates a new custom target state.
+  $values = array(
+    // Fixed values for new transition.
+    'wid' => $context['workflow']->wid,
+    'sid' => $context['state']->sid,
+
+    // Custom values for new transition.
+    // The ID must be an integer, due to db-table constraints.
+    'target_sid' => '998',
+    'label' => 'go to my new fantasy state',
+  );
+  $new_transition = new WorkflowConfigTransition($values);
+
+  $transitions[] = $new_transition;
 }
+
+
+/**********************************************************************
+ * Hooks defined by core Form API: hooks to to alter the Workflow Form/Widget.
+ */
+
+/**
+ * Alter forms for field widgets provided by other modules.
+ *
+ * @param $element
+ *   The field widget form element as constructed by hook_field_widget_form().
+ * @param $form_state
+ *   An associative array containing the current state of the form.
+ * @param $context
+ *   An associative array containing the following key-value pairs, matching the
+ *   arguments received by hook_field_widget_form():
+ *   - form: The form structure to which widgets are being attached. This may be
+ *     a full form structure, or a sub-element of a larger form.
+ *   - field: The field structure.
+ *   - instance: The field instance structure.
+ *   - langcode: The language associated with $items.
+ *   - items: Array of default values for this field.
+ *   - delta: The order of this item in the array of subelements (0, 1, 2, etc).
+ *
+ * @see hook_field_widget_form()
+ * @see hook_field_widget_WIDGET_TYPE_form_alter()
+ */
+function hook_field_widget_form_alter(&$element, &$form_state, $context) {
+  // A hook for changing any widget. Better not use it: it is called on EVERY
+  // Widget. (Even though the message is only shown once.)
+  // D7: This hook is introduced in Drupal 7.8.
+  // workflow_debug(__FILE__, __FUNCTION__, __LINE__, '', '');
+  // dpm($context['widget']->getPluginId());
+}
+
+function hook_field_widget_workflow_default_form_alter(&$element, $form_state, $context) {
+  // A hook specific for the 'workflow_default' widget.
+  // D7: This hook is introduced in Drupal 7.8.
+  // D8: This name is specified in the annotation of WorkflowDefaultWidget.
+  // workflow_debug(__FILE__, __FUNCTION__, __LINE__, '', '');
+  // dpm($context['widget']->getPluginId());
+
+  // A widget on an entity form.
+
+  if ('workflow_default' == $context['widget']->getPluginId()) { // D8-code
+    // This object contains all you need. You may find it in one of two locations.
+    /* @var $transition WorkflowTransitionInterface */
+    $transition = $element['#default_value'];
+
+    // An example of customizing/overriding the workflow widget.
+    // Beware, until now, you must do this twice: on the widget and on the form.
+    if ($transition->getOwnerId() == 1) {
+      drupal_set_message('I got you, user 1, you will never schedule again,
+        and you WILL document each state change!', 'warning');
+      // Let's prohibit scheduling for user 1.
+      $element['workflow']['workflow_scheduling']['#access'] = FALSE;
+      // Let's prohibit scheduling for user 1.
+      if ($element['workflow']['workflow_comment']['#access'] == TRUE) {
+        $element['workflow']['workflow_comment']['#required'] = TRUE;
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_form_BASE_FORM_ID_alter().
+ *
+ * Use this hook to alter the form.
+ * It is only suited if you only use View Page or Workflow Tab.
+ * If you change the state on the Node Form (Edit modus), you need the hook
+ * hook_form_alter(). See below for more info.
+ */
+function hook_form_workflow_transition_form_alter(&$form, &$form_state, $form_id) {
+
+  // Get the Entity.
+  $entity = $form['workflow']['workflow_entity']['#value'];
+  $entity_type = $form['workflow']['workflow_entity_type']['#value'];
+
+  // Use the complicated form, which is suited for all Entity types.
+  // For nodes only: $entity_type = 'node'; $entity_bundle = $entity->type;
+  list(, , $entity_bundle) = entity_extract_ids($entity_type, $entity);
+
+  // Get the current State ID.
+  $sid = workflow_node_current_state($entity, $entity_type, $field_name = NULL);
+  // Get the State object, if needed.
+  $state = workflow_state_load($sid);
+
+  // Change the form, depending on the state ID.
+  // In the upcoming version 7.x-2.4, States should have a machine_name, too.
+  if ($entity_type == 'node' && $entity_bundle == 'MY_NODE_TYPE') {
+    switch ($state->sid) {
+      case '2':
+        // Change form element, form validate and form submit for state '2'.
+        break;
+
+      case '3':
+        // Change form element, form validate and form submit for state '3'.
+        break;
+    }
+  }
+
+}
+
+/**
+ * Implements hook_form_alter().
+ *
+ * Use this hook to alter the form on a Node Form, Comment Form (Edit page).
+ */
+function hook_form_alter(&$form, $form_state, $form_id) {
+
+  // Get the Entity.
+  $entity = $form['#entity'];
+  $entity_type = $form['#entity_type'];
+  // Use the complicated form, which is suited for all Entity Types.
+  list(, , $entity_bundle) = entity_extract_ids($entity_type, $entity);
+
+  // Discover if this is the correct form.
+  // ...
+  // Get the current state and act upon it.
+  // .. copy code from the hook above.
+}
+
+/*
+ * Implements hook_field_attach_form().
+ */
+function workflow_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode){
+  // @see http://drupal.stackexchange.com/questions/101857/difference-between-hook-form-alter-and-hook-field-attach-form
+}
+

+ 75 - 0
sites/all/modules/contrib/admin/workflow/workflow.block.inc

@@ -0,0 +1,75 @@
+<?php
+/**
+ * @file
+ * Provide block with Workflow form.
+ *
+ * Credits to workflow_extensions module.
+ */
+
+/**
+ * Implements hook_block_info().
+ *
+ * Re-implements the block from workflow_extensions module.
+ */
+function workflow_block_info() {
+  $blocks['workflow_transition'] = array(
+    'info' => t('Workflow transition form'),
+    'cache' => DRUPAL_NO_CACHE, // DRUPAL_CACHE_PER_ROLE will be assumed.
+  );
+
+  return $blocks;
+}
+
+/**
+ * Implements hook_block_view().
+ */
+function workflow_block_view($delta) {
+  $block = array();
+  $form = array();
+
+  // @todo: how to make this work for non-nodes, like terms?
+  $entity = NULL;
+  if ((arg(0) == 'node') && (arg(1) !== NULL) ) {
+    $entity_type = arg(0);
+    $entity_id = arg(1);
+    $entity = entity_load_single($entity_type, $entity_id);
+  }
+
+  if ($entity) {
+    list($entity_id, , $entity_bundle) = entity_extract_ids($entity_type, $entity);
+
+    if (is_null($field_name = workflow_get_field_name($entity, $entity_type, NULL, $entity_id))) {
+      return $block;
+    }
+
+    // Get the current sid.
+    $current_sid = workflow_node_current_state($entity, $entity_type, $field_name);
+    $current_state = ($current_sid) ? workflow_state_load_single($current_sid) : NULL;
+    $workflow = ($current_state) ? $current_state->getWorkflow() : NULL;
+    if (!$workflow) {
+      return $block;
+    }
+
+    // Show the current state and the Workflow form to allow state changing.
+    // N.B. This part is replicated in hook_node_view, workflow_tab_page, workflow_vbo, transition_edit.
+    // @todo: support multiple workflows per entity.
+    // For workflow_tab_page with multiple workflows, use a separate view. See [#2217291].
+    $field = _workflow_info_field($field_name, $workflow);
+    $field_id = $field['id'];
+    $instance = field_info_instance($entity_type, $field_name, $entity_bundle);
+    if (!$field_id) {
+      // This is a Workflow Node workflow. Set widget options as in v7.x-1.2
+      $field['settings']['widget']['comment'] = isset($workflow->options['comment_log_tab']) ? $workflow->options['comment_log_tab'] : 1; // vs. ['comment_log_node'];
+      $field['settings']['widget']['current_status'] = TRUE;
+    }
+
+    $form_id = implode('_', array('workflow_transition_form', $entity_type, $entity_id, $field_id));
+    $form += drupal_get_form($form_id, $field, $instance, $entity_type, $entity);
+
+    $block['content'] = $form;
+    if ($block['content']) {
+      $block['subject'] = t('Current state: @state', array('@state' => $current_state->label()));
+    }
+  }
+  return $block;
+}

+ 740 - 0
sites/all/modules/contrib/admin/workflow/workflow.deprecated.inc

@@ -0,0 +1,740 @@
+<?php
+
+/**
+ * @file
+ * Contains contains per-class functions, that are deprecated.
+ *
+ * Usage: The new code can be tested, by removing this file-include from workflow.module.
+ */
+
+/**
+ * Deprecated functions related to table {workflows}.
+ *
+ * These are replaced by methods of class Workflow.
+ */
+
+/**
+ * Get all workflows.
+ *
+ * @deprecated: workflow_get_workflows --> workflow_load_multiple
+ */
+function workflow_get_workflows() {
+  return workflow_load_multiple();
+}
+
+/**
+ * Get a specific workflow, wid is a unique ID.
+ *
+ * @deprecated: workflow_get_workflows_by_wid --> workflow_load_single
+ */
+function workflow_get_workflows_by_wid($wid, $reset = FALSE) {
+  return workflow_load_single($wid, $reset);
+}
+
+/**
+ * Get a specific workflow, name is a unique ID.
+ *
+ * @deprecated: workflow_get_workflows_by_name --> workflow_load_by_name
+ */
+function workflow_get_workflows_by_name($name) {
+  return workflow_load_by_name($name);
+}
+
+/**
+ * Helper function, to get the label of a given workflow.
+ *
+ * @deprecated: workflow_get_wid_label --> workflow_label
+ */
+function workflow_get_wid_label($wid) {
+  if (empty($wid)) {
+    $output = t('No workflow');
+  }
+  elseif ($workflow = workflow_load_single($wid)) {
+    $output = $workflow->label();
+  }
+  else {
+    $output = t('Unknown workflow');
+  }
+  return $output;
+}
+
+/**
+ * Return the ID of the creation state for this workflow.
+ *
+ * @param mixed $wid
+ *   The ID of the workflow.
+ *
+ * @return int
+ *
+ * @deprecated: workflow_get_creation_state_by_wid($wid) --> $workflow->getCreationSid().
+ */
+function workflow_get_creation_state_by_wid($wid) {
+  $sid = 0;
+  if ($workflow = workflow_load_single($wid)) {
+    $sid = $workflow->getCreationSid();
+  }
+  return $sid;
+}
+
+/**
+ * Return the ID of the creation state given a content type.
+ *
+ * @param string $type
+ *   The type of the content.
+ */
+//function workflow_get_creation_state_by_type($type) {
+//  $sid = FALSE;
+//
+//  if ($workflow = workflow_get_workflows_by_type($type, 'node')) {
+//    $sid = $workflow->getCreationSid();
+//  }
+//  return $sid;
+//}
+
+/**
+ * Given information, update or insert a new workflow. Returns data by ref. (like node_save).
+ *
+ * @deprecated: workflow_update_workflows() --> Workflow->save()
+ */
+//function workflow_update_workflows(&$data, $create_creation_state = TRUE) {
+//  $data = (object) $data;
+//  if (isset($data->tab_roles) && is_array($data->tab_roles)) {
+//    $data->tab_roles = serialize($data->tab_roles);
+//  }
+//
+//  if (isset($data->wid) && workflow_load_single($data->wid)) {
+//    drupal_write_record('workflows', $data, 'wid');
+//  }
+//  else {
+//    drupal_write_record('workflows', $data);
+//    if ($create_creation_state) {
+//      $state_data = array(
+//        'wid' => $data->wid,
+//        'state' => t(WORKFLOW_CREATION_STATE_NAME),
+//        'sysid' => WORKFLOW_CREATION,
+//        'weight' => WORKFLOW_CREATION_DEFAULT_WEIGHT,
+//        );
+//
+//      workflow_update_workflow_states($state_data);
+//      // @TODO consider adding state data to return here as part of workflow data structure.
+//      // That way we could past structs and transitions around as a data object as a whole.
+//      // Might make clone easier, but it might be a little hefty for our needs?
+//    }
+//  }
+//}
+
+/**
+ * Given a wid, delete the workflow and its data.
+ *
+ * @deprecated: workflow_delete_workflows_by_wid() --> Workflow::delete().
+ */
+function workflow_delete_workflows_by_wid($wid) {
+  $workflow = workflow_load_single($wid);
+  $workflow->delete();
+}
+
+/**
+ * Deprecated functions related to table {workflow_states}.
+ *
+ * These are replaced by methods of class WorkflowState.
+ */
+
+/**
+ * Get all active states in the system.
+ *
+ * @return array
+ *   A keyed array $id => $name, of all active states.
+ *
+ * @deprecated: workflow_get_workflow_states_all() --> Workflow::getOptions()
+ */
+function workflow_get_workflow_states_all() {
+  // Get all states, only where active.
+  return workflow_get_workflow_state_names($wid = 0, $grouped = FALSE, $all = FALSE);
+}
+
+/**
+ * Menu access control callback. Determine access to Workflow tab.
+ *
+ * @deprecated workflow_node_tab_access() --> workflow_tab_access().
+ */
+function workflow_node_tab_access($node = NULL) {
+  if ($node == NULL) {
+    return FALSE;
+  }
+  return workflow_tab_access('node', $node);
+}
+
+/**
+ * Get the states current user can move to for a given node.
+ *
+ * @param object $node
+ *   The node to check.
+ * @param bool $force
+ *   A switch to enable access to all states (e.g., for Rules).
+ * @param State $state
+ *   The predetermined state object (v7.x-1.3: new parameter for Workflow Field).
+ *
+ * @return array
+ *   Array of transitions.
+ *
+ * @deprecated workflow_field_choices() --> WorkflowState->getOptions()
+ */
+function workflow_field_choices($node, $force = FALSE, $state = NULL) {
+  global $user; // Alert: In 7.x-2.4, getOptions() has a new $user parameter.
+
+  $choices = array();
+
+  if (!$node) {
+    // If no node is given, no result. (e.g., on a Field settings page.)
+    return $choices;
+  }
+
+  if ($state) {
+    // This is used in Field API. A state object is already passed in.
+  }
+  else {
+    // This is used in Node API.
+    $field_name = ''; // An explicit var is needed.
+    $current_sid = workflow_node_current_state($node, 'node', $field_name);
+    $state = workflow_state_load_single($current_sid);
+  }
+  return $state->getOptions('node', $node, '', $user, $force);
+}
+
+/**
+ * Determine if the Workflow Form must be shown.
+ * If not, a formatter must be shown, since there are no valid options.
+ *
+ * @param mixed $sid
+ *   the current state ID.
+ * @param Workflow $workflow
+ *   the workflow object (might be derived from $sid).
+ * @param array $options
+ *   an array with $id => $label options, as determined in WorkflowState->getOptions().
+ *
+ * @return bool $show_widget
+ *   TRUE = a form must be shown; FALSE = no form, a formatter must be shown instead.
+ *
+ * @deprecated workflow_show_form() --> WorkflowState->showWidget()
+ */
+// function workflow_show_form($sid, $workflow, array $options) {
+//   $state = workflow_state_load_single($sid);
+//   return !$state->showWidget($options);
+// }
+
+/**
+ * Validate target state and either execute a transition immediately or schedule
+ * a transition to be executed later by cron.
+ *
+ * @param object $entity
+ * @param string $new_sid
+ *   An integer; the target state ID.
+ * @param bool $force
+ *   Allows bypassing permissions, primarily for Rules.
+ * @param array $field
+ *   The field structure for the operation.
+ *
+ * @deprecated: workflow_transition --> WorkflowDefaultWidget::submit()
+ */
+function workflow_transition($entity, $new_sid, $force = FALSE, $field = array()) {
+  $entity_type = 'node'; // Entity support is in workflow_transition --> WorkflowDefaultWidget::submit()
+  // @todo: do not use widget:submit directly, use workflow_entity_save instead.
+  $widget = new WorkflowDefaultWidget($field, $instance = array(), $entity_type, $entity);
+  $form = array();
+  $form_state = array();
+  $items = array();
+  $items[0]['workflow'] = (array) $entity;
+  $items[0]['workflow']['workflow_sid'] = $new_sid;
+  $widget->submit($form, $form_state, $items, $force);
+}
+
+/**
+ * Get all states in the system by content type.
+ */
+function workflow_get_workflow_states_by_type($type) {
+  $query = "SELECT ws.sid, ws.wid, ws.state, ws.weight, ws.sysid "
+    . "FROM {workflow_type_map} wtm "
+    . "INNER JOIN {workflow_states} ws ON ws.wid = wtm.wid "
+    . "WHERE wtm.type = :type AND ws.status = 1 "
+    . "ORDER BY ws.weight, ws.sid ";
+  $query_array = array(':type' => $type);
+  $results = db_query($query, $query_array);
+  return $results->fetchAll();
+}
+
+/**
+ * Get all states in the system, with options to filter, only where a workflow exists.
+ *
+ * @deprecated: workflow_get_workflow_states() --> WorkflowState::getStates()
+ * @deprecated: workflow_get_workflow_states_by_wid() --> WorkflowState::getStates()
+ */
+function workflow_get_workflow_states($options = array()) {
+  // Build the basic query.
+  $query = db_select('workflow_states', 'ws');
+  $query->leftJoin('workflows', 'w', 'w.wid = ws.wid');
+  $query->fields('ws');
+  $query->addField('w', 'wid');
+  $query->addField('w', 'name');
+
+  // Spin through the options and add conditions.
+  foreach ($options as $column => $value) {
+    $query->condition('ws.' . $column, $value);
+  }
+
+  // Set the sorting order.
+  $query->orderBy('ws.wid');
+  $query->orderBy('ws.weight');
+
+  // Just for grins, add a tag that might result in modifications.
+  $query->addTag('workflow_states');
+
+  // Give them the answer.
+  return $query->execute()->fetchAllAssoc('sid');
+}
+
+/**
+ * Get all states in the system, with options to filter, only where a workflow exists.
+ *
+ * @deprecated: workflow_get_workflow_states_by_wid --> Workflow->getOptions
+ */
+function workflow_get_workflow_states_by_wid($wid, $options = array()) {
+  $options['wid'] = $wid;
+  return workflow_get_workflow_states($options);
+}
+
+/**
+ * Given a sid, return a workflow. Sids are a unique id.
+ *
+ * @deprecated: workflow_get_workflow_by_sid --> workflow_state_load_single
+ */
+function workflow_get_workflow_by_sid($sid) {
+  return db_query("SELECT w.wid, w.name, w.tab_roles, w.options FROM {workflow_states} s
+    INNER JOIN {workflows} w ON w.wid=s.wid WHERE sid = :sid ",
+    array(':sid' => $sid))->fetchObject();
+}
+
+/**
+ * Given a sid, return a state. Sids are a unique id.
+ *
+ * @deprecated: workflow_get_workflow_states_by_sid --> workflow_state_load_single
+ */
+function workflow_get_workflow_states_by_sid($sid, $options = array()) {
+  static $sids = array();
+  if (!isset($sids[$sid])) {
+    $states = workflow_get_workflow_states(array('sid' => $sid));
+    $sids[$sid] = reset($states);
+  }
+  return $sids[$sid];
+}
+
+/**
+ * Given a sid, return all other states in that workflow.
+ *
+ * @deprecated: replaced by WorkflowState::getStates($sid)
+ */
+function workflow_get_other_states_by_sid($sid) {
+  $query = "SELECT sid, state "
+    . "FROM {workflow_states} "
+    . "WHERE wid = (SELECT wid FROM {workflow_states} WHERE sid = :sid AND status = 1 AND sysid = 0) ";
+  return db_query($query, array(':sid' => $sid))->fetchAllKeyed();
+}
+
+/**
+ * Given a wid and state, return a state. Wids / states are a unique id.
+ */
+function workflow_get_workflow_states_by_wid_state($wid, $state) {
+  $options = array(
+    'state' => $state,
+    'wid' => $wid,
+  );
+  return workflow_get_workflow_states($options);
+}
+
+/**
+ * Given a sid, delete the state and all associated data.
+ *
+ * @deprecated: workflow_delete_workflow_states_by_sid($sid, $new_sid, $true_delete) --> WorkflowState->delete()
+ */
+function workflow_delete_workflow_states_by_sid($sid, $new_sid = FALSE, $true_delete = FALSE) {
+  if ($state = workflow_state_load_single($sid)) {
+    $state->delete($new_sid, $true_delete);
+  }
+}
+
+/**
+ * Save (update/insert) a Workflow State into table {workflow_states}.
+ *
+ * @deprecated: workflow_update_workflow_states() --> WorkflowState->save()
+ */
+function workflow_update_workflow_states(&$data) {
+  $data = (object) $data;
+  if (!isset($data->sysid)) {
+    $data->sysid = 0;
+  }
+  if (!isset($data->status)) {
+    $data->status = 1;
+  }
+  if (isset($data->sid) && workflow_state_load_single($data->sid)) {
+    drupal_write_record('workflow_states', $data, 'sid');
+  }
+  else {
+    drupal_write_record('workflow_states', $data);
+  }
+}
+
+/**
+ * Functions related to table workflow_transitions.
+ */
+
+/**
+ * Given a wid get the transitions.
+ *
+ * @deprecated: workflow_get_workflow_transitions_by_wid() ==> Workflow->loadTransitions()
+ */
+function workflow_get_workflow_transitions_by_wid($wid) {
+  static $transitions;
+  if (!isset($transitions[$wid])) {
+    $query = 'SELECT t.tid, t.sid, t.target_sid, t.roles, s1.wid '
+      . 'FROM {workflow_transitions} t '
+      . 'INNER JOIN {workflow_states} s1 ON t.sid=s1.sid '
+      . 'INNER JOIN {workflow_states} s2 ON t.target_sid=s2.sid '
+      . 'WHERE s1.wid = :wid AND s2.wid = :wid';
+    $transitions[$wid] = db_query('SELECT t.*, s1.wid FROM {workflow_transitions} AS t INNER JOIN {workflow_states} AS s1 ON t.sid=s1.sid INNER JOIN {workflow_states} AS s2 ON t.target_sid=s2.sid WHERE s1.wid = :wid AND s2.wid = :wid',
+      array(':wid' => $wid))->fetchAll();
+  }
+  return $transitions[$wid];
+}
+
+/**
+ * Given a tid, get the transition. It is a unique object, only one return.
+ */
+function workflow_get_workflow_transitions_by_tid($tid) {
+  static $transitions;
+  if (!isset($transitions[$tid])) {
+    $transitions[$tid] = db_query('SELECT tid, sid, target_sid, roles FROM {workflow_transitions} WHERE tid = :tid',
+      array(':tid' => $tid))->fetchObject();
+  }
+  return $transitions[$tid];
+}
+
+/**
+ * Given a sid, get the transition.
+ *
+ * @deprecated: workflow_get_workflow_transitions_by_sid --> workflow_state_load_single
+ */
+//function workflow_get_workflow_transitions_by_sid($sid) {
+//  static $transitions;
+//  if (!isset($transitions[$sid])) {
+//    $transitions[$sid] = db_query('SELECT tid, sid, target_sid, roles FROM {workflow_transitions} WHERE sid = :sid',
+//      array(':sid' => $sid))->fetchAll();
+//  }
+//  return $transitions[$sid];
+//}
+
+/**
+ * Given a target_sid, get the transition.
+ *
+ * @deprecated: workflow_get_workflow_transitions_by_sid --> Workflow::getTransitionsByTargetSid
+ */
+//function workflow_get_workflow_transitions_by_target_sid($target_sid) {
+//  static $transitions;
+//  if (!isset($transitions[$target_sid])) {
+//    $transitions[$target_sid] = db_query('SELECT tid, sid, target_sid, roles FROM {workflow_transitions} WHERE target_sid = :target_sid',
+//      array(':target_sid' => $target_sid))->fetchAll();
+//  }
+//  return $transitions[$target_sid];
+//}
+
+/**
+ * Given a sid get any transition involved.
+ *
+ * @deprecated
+ */
+//function workflow_get_workflow_transitions_by_sid_involved($sid) {
+//  $results = db_query('SELECT tid, sid, target_sid, roles FROM {workflow_transitions} WHERE sid = :sid OR target_sid = :sid', array(':sid' => $sid));
+//  return $results->fetchAll();
+//}
+
+/**
+ * Given a role string get any transition involved.
+ *
+ * @deprecated
+ */
+//function workflow_get_workflow_transitions_by_roles($roles) {
+//  $results = db_query('SELECT tid, sid, target_sid, roles FROM {workflow_transitions} WHERE roles LIKE :roles', array(':roles' => $roles));
+//  return $results->fetchAll();
+//}
+
+/**
+ * Given a sid and target_sid, get the transition. This will be unique.
+ *
+ * @deprecated: workflow_get_workflow_transitions_by_sid_target_sid ==> $workflow->getTransitionsBySidTargetSid
+ */
+//function workflow_get_workflow_transitions_by_sid_target_sid($sid, $target_sid) {
+//  $results = db_query('SELECT tid, sid, target_sid, roles FROM {workflow_transitions} WHERE sid = :sid AND target_sid = :target_sid', array(':sid' => $sid, ':target_sid' => $target_sid));
+//  return $results->fetchObject();
+//}
+
+/**
+ * Given a tid, delete the transition.
+ *
+ * @deprecated: workflow_delete_workflow_transitions_by_tid ==> entity_delete($transition)
+ */
+function workflow_delete_workflow_transitions_by_tid($tid) {
+  // Notify any interested modules before we delete, in case data is needed.
+  module_invoke_all('workflow', 'transition delete', $tid, NULL, NULL, FALSE);
+
+  return db_delete('workflow_transitions')->condition('tid', $tid)->execute();
+}
+
+/**
+ * Given a sid and target_sid, get the transition. This will be unique.
+ */
+function workflow_delete_workflow_transitions_by_roles($roles) {
+  // NOTE: This allows us to send notifications out.
+  foreach (workflow_get_workflow_transitions_by_roles($roles) as $transition) {
+    workflow_delete_workflow_transitions_by_tid($transition->tid);
+  }
+}
+
+/**
+ * Given data, insert or update a workflow_transitions.
+ *
+ * @deprecated: workflow_update_workflow_transitions() ==> entity_save('WorkflowConfigTransition', $transition)
+ */
+//function workflow_update_workflow_transitions(&$data) {
+//  $data = (object) $data;
+//  $transition = workflow_get_workflow_transitions_by_sid_target_sid($data->sid, $data->target_sid);
+//  if ($transition) {
+//    $roles = explode(',', $transition->roles);
+//    foreach (explode(',', $data->roles) as $role) {
+//      if (array_search($role, $roles) === FALSE) {
+//        $roles[] = $role;
+//      }
+//    }
+//    $transition->roles = implode(',', $roles);
+//    drupal_write_record('workflow_transitions', $transition, 'tid');
+//    $data = $transition;
+//  }
+//  else {
+//    drupal_write_record('workflow_transitions', $data);
+//  }
+//}
+
+/**
+ * Given a tid and new roles, update them.
+ *
+ * @todo - this should be refactored out, and the update made a full actual update.
+ */
+//function workflow_update_workflow_transitions_roles($tid, $roles) {
+//  return db_update('workflow_transitions')->fields(array('roles' => implode(',', $roles)))->condition('tid', $tid, '=')->execute();
+//}
+
+/**
+ * Get allowable transitions for a given workflow state.
+ *
+ * Typical use:
+ * "global $user;
+ * "$possible = workflow_allowable_transitions($sid, 'to', $user->roles);
+ *
+ * If the state ID corresponded to the state named "Draft", $possible now
+ * contains the states that the current user may move to from the Draft state.
+ *
+ * @param $sid
+ *   The ID of the state in question.
+ * @param $dir
+ *   The direction of the transition: 'to' or 'from' the state denoted by $sid.
+ *   When set to 'to' all the allowable states that may be moved to are
+ *   returned; when set to 'from' all the allowable states that may move to the
+ *   current state are returned.
+ * @param mixed $roles
+ *   Array of ints (and possibly the string 'author') representing the user's
+ *   roles. If the string 'ALL' is passed (instead of an array) the role
+ *   constraint is ignored (this is the default for backwards compatibility).
+ *
+ * @return array
+ *   Associative array of ($sid => $state_name), excluding current state.
+ *
+ * @deprecated: workflow_allowable_transitions() => Workflow::getTransitionsBySid()
+ */
+//function workflow_allowable_transitions($sid, $dir = 'to', $roles = 'ALL') {
+//  $transitions = array();
+//
+//  // Main query from transitions table.
+//  $query = db_select('workflow_transitions', 't')
+//    ->fields('t', array('tid'));
+//
+//  if ($dir == 'to') {
+//    $query->innerJoin('workflow_states', 's', 's.sid = t.target_sid');
+//    $query->addField('t', 'target_sid', 'state_id');
+//    $query->condition('t.sid', $sid);
+//  }
+//  else {
+//    $query->innerJoin('workflow_states', 's', 's.sid = t.sid');
+//    $query->addField('t', 'sid', 'state_id');
+//    $query->condition('t.target_sid', $sid);
+//  }
+//
+//  $query->addField('s', 'state', 'state_name');
+//  $query->addField('s', 'weight', 'state_weight');
+//  $query->addField('s', 'sysid');
+//  $query->condition('s.status', 1);
+//
+//  // Now let's get the current state.
+//  $query2 = db_select('workflow_states', 's');
+//  $query2->addField('s', 'sid', 'tid');
+//  $query2->addField('s', 'sid', 'state_id');
+//  $query2->addField('s', 'state', 'state_name');
+//  $query2->addField('s', 'weight', 'state_weight');
+//  $query2->addField('s', 'sysid');
+//  $query2->condition('s.status', 1);
+//  $query2->condition('s.sid', $sid);
+//
+//  $query2->orderBy('state_weight');
+//  $query2->orderBy('state_id');
+//
+//  // Add the union of the two queries.
+//  $query->union($query2, 'UNION');
+//
+//  $results = $query->execute();
+//
+//  foreach ($results as $transition) {
+//    if ($roles == 'ALL'  // Superuser.
+//      || $sid == $transition->state_id // Include current state for same-state transitions.
+//      || workflow_transition_allowed($transition->tid, $roles)) {
+//      $transitions[] = $transition;
+//      // $transitions[$transition->tid] = $transition; //@todo
+//    }
+//  }
+//  return $transitions;
+//}
+
+/**
+ * See if a transition is allowed for a given role.
+ *
+ * @param int $tid
+ *   A transition ID.
+ * @param mixed $role
+ *   A single role (int or string 'author') or array of roles.
+ *
+ * @return bool
+ *   TRUE if the role is allowed to do the transition.
+ *
+ * @deprecated: workflow_transition_allowed => WorkflowConfigTransition::isAllowed
+ */
+function workflow_transition_allowed($tid, $role = NULL) {
+  $config_transitions = entity_load('WorkflowConfigTransition', array($tid));
+  $config_transition = reset($config_transitions);
+
+  if ($role) {
+    if (!is_array($role)) {
+      $role = array($role);
+    }
+    $allowed = $config_transition->roles;
+    return array_intersect($role, $allowed) == TRUE;
+  }
+}
+
+
+/**
+ * Deprecated functions related to table {workflow_scheduled_transition}.
+ *
+ * These are replaced by methods of class WorkflowScheduledTransition.
+ */
+
+/**
+ * Given a node, get all scheduled transitions for it.
+ *
+ * @deprecated: workflow_get_workflow_scheduled_transition_by_nid() --> WorkflowScheduledTransition::load()
+ */
+function workflow_get_workflow_scheduled_transition_by_nid($nid) {
+  return WorkflowScheduledTransition::load('node', $nid);
+}
+
+/**
+ * Given a timeframe, get all scheduled transitions.
+ *
+ * @deprecated: workflow_get_workflow_scheduled_transition_by_between() --> WorkflowScheduledTransition::loadBetween()
+ */
+function workflow_get_workflow_scheduled_transition_by_between($start = 0, $end = REQUEST_TIME) {
+  return WorkflowScheduledTransition::loadBetween($start, $end);
+}
+
+/**
+ * Insert a new scheduled transition. Only one transition at a time (for now).
+ *
+ * @deprecated: workflow_insert_workflow_scheduled_transition() --> WorkflowScheduledTransition::save()
+ */
+function workflow_insert_workflow_scheduled_transition($data) {
+  $data = (object) $data;
+  workflow_delete_workflow_scheduled_transition_by_nid($data->nid);
+  drupal_write_record('workflow_scheduled_transition', $data);
+}
+
+/**
+ * Given a node, delete transitions for it.
+ *
+ * @deprecated: workflow_delete_workflow_scheduled_transition_by_nid() --> WorkflowScheduledTransition::delete()
+ */
+// function workflow_delete_workflow_scheduled_transition_by_nid($nid) {
+//   return WorkflowScheduledTransition::deleteById('node', $nid);
+// }
+
+/**
+ * Deprecated functions related to table {workflow_node}.
+ */
+
+/**
+ * Given nid, update the new stamp. This probably can be refactored. Called by workflow_execute_transition().
+ *
+ * @deprecated: this is micro-optimalisation.
+ */
+function workflow_update_workflow_node_stamp($nid, $new_stamp) {
+  return db_update('workflow_node')->fields(array('stamp' => $new_stamp))->condition('nid', $nid, '=')->execute();
+}
+
+/**
+ * Deprecated functions related to table {workflow_node_history}.
+ */
+
+/**
+ * Get most recent transition for a node.
+ *
+ * @deprecated: workflow_get_recent_node_history() --> workflow_transition_load_single()
+ */
+function workflow_get_recent_node_history($nid) {
+  $field_name = '';
+  return workflow_transition_load_single('node', $nid, $field_name);
+}
+
+/**
+ * Get all recorded history for a node id.
+ *
+ * Since this may return a lot of data, a limit is included to allow for only one result.
+ */
+function workflow_get_workflow_node_history_by_nid($nid, $limit = NULL) {
+  $field_name = '';
+  return workflow_transition_load_multiple('node', array($nid), $field_name, $limit);
+}
+
+/**
+ * Given data, insert a new history. Always insert.
+ *
+ * @deprecated: workflow_insert_workflow_node_history() --> WorkflowTransition::save()
+ */
+function workflow_insert_workflow_node_history($data) {
+  $data = (object) $data;
+  if (isset($data->hid)) {
+    unset($data->hid);
+  }
+
+  // Check for no transition.
+  if ($data->old_sid == $data->sid) {
+    // Make sure we haven't already inserted history for this update.
+    $last_history = workflow_get_workflow_node_history_by_nid($data->nid, 1);
+    if (isset($last_history) && $last_history->stamp == REQUEST_TIME) {
+      return;
+    }
+  }
+  drupal_write_record('workflow_node_history', $data);
+}

+ 589 - 0
sites/all/modules/contrib/admin/workflow/workflow.entity.inc

@@ -0,0 +1,589 @@
+<?php
+
+/**
+ * @file
+ * Integrates workflow with entity API.
+ */
+
+/**
+ * Implements hook_entity_info().
+ *
+ * @todo: implement hook_property_info, metadata.
+ */
+function workflow_entity_info() {
+
+  $entities['Workflow'] = array(
+    'label' => t('Workflow'),
+    'plural label' => t('Workflows'),
+    'entity class' => 'Workflow',
+    'controller class' => 'WorkflowController',
+    'metadata controller class' => 'EntityDefaultMetadataController',
+    'features controller class' => 'WorkflowFeaturesController',
+    'module' => 'workflow',
+    'base table' => 'workflows',
+    // Make WorkflowTransition fieldable. The wid is the Transition Type.
+    'fieldable' => FALSE,
+    'bundle of' => 'WorkflowTransition',
+
+    'exportable' => TRUE,
+    'entity keys' => array(
+      'id' => 'wid',
+      'name' => 'name',
+    ),
+    'label callback' => 'entity_class_label',
+    'uri callback' => 'entity_class_uri',
+    // The following is added in workflow_admin_ui.module.
+    /*
+    // 'access callback' => 'workflow_access',
+    // 'admin ui' => array(
+    //   'path' => 'admin/config/workflow/workflow',
+    //   'file' => 'workflow_admin_ui/workflow_admin_ui.pages.inc',
+    //   'controller class' => 'EntityWorkflowUIController',
+    //   'menu wildcard' => '%workflow',
+    // ),
+     */
+  );
+
+  $entities['WorkflowState'] = array(
+    'label' => t('Workflow state'),
+    'entity class' => 'WorkflowState',
+    'controller class' => 'WorkflowStateController',
+    'metadata controller class' => 'EntityDefaultMetadataController',
+    // 'features controller class' => FALSE, //@todo: implement this.
+    'module' => 'workflow',
+    'base table' => 'workflow_states',
+    'fieldable' => FALSE,
+    'exportable' => FALSE,
+    'entity keys' => array(
+      'id' => 'sid',
+    ),
+    'label callback' => 'entity_class_label',
+    'uri callback' => 'entity_class_uri',
+  );
+
+  $entities['WorkflowConfigTransition'] = array(
+    'label' => t('Workflow config transition'),
+    'entity class' => 'WorkflowConfigTransition',
+    // Add controller Class. 'Workflow' class is the de-facto controller.
+    'controller class' => 'WorkflowConfigTransitionController',
+    'metadata controller class' => 'EntityDefaultMetadataController',
+    'base table' => 'workflow_transitions',
+    'exportable' => FALSE,
+    'module' => 'workflow',
+    'entity keys' => array(
+      'id' => 'tid',
+      'status' => 'status',
+    ),
+    'label callback' => 'entity_class_label',
+    // 'uri callback' => 'entity_class_uri',
+/*
+    'view modes' => array(
+      'full' => array(
+        'label' => t('Full'),
+        'custom settings' => TRUE,
+      ),
+    ),
+    'views controller class' => 'EntityDefaultViewsController',
+    'access callback' => 'workflow_tab_access', // @todo: use to-be workflow_access here. Access to Tab <> access to workflow.
+ */
+  );
+
+  // The Controller class of Transitions and ScheduledTransition is shared.
+  $entities['WorkflowTransition'] = array(
+    'label' => t('Workflow executed transition'),
+    'entity class' => 'WorkflowTransition',
+    'controller class' => 'WorkflowTransitionController',
+    'metadata controller class' => 'EntityDefaultMetadataController',
+    // Do not use 'extra fields controller class'. Use hook_field_extra_fields instead.
+    // 'extra fields controller class' => 'EntityDefaultExtraFieldsController',
+    'views controller class' => 'EntityDefaultViewsController',
+    'rules controller class' => 'EntityDefaultRulesController',
+    'features controller class' => FALSE,
+    'base table' => 'workflow_node_history',
+
+    'fieldable' => TRUE,
+    // Bundles are defined by the $types below.
+    'bundles' => array(),
+    // Bundle keys tell the FieldAPI how to extract information from the bundle objects.
+    'bundle keys' => array(
+      'bundle' => 'wid',
+    ),
+
+//    'admin ui' => array(
+//      'path' => 'admin/config/workflow/workflow/manage/%workflow_transition/fields2',
+//      'menu wildcard' => '%workflow_transition',
+//      'controller class' => 'EntityDefaultUIController',
+//    ),
+
+    'entity keys' => array(
+      'id' => 'hid',
+      'bundle'=> 'wid',
+    ),
+    'label callback' => 'entity_class_label',
+    'module' => 'workflow',
+  );
+
+  // Add bundle info but bypass entity_load() as we cannot use it here.
+  // (The bundle-setup is copied from the D7 entityform module.)
+  // Get all workflows, keyed by wid.
+  // Use try..catch.. to workaround #1311820, @see #2484325.
+  try {
+    // Add bundle info but bypass entity_load() as we cannot use it here.
+    // (The bundle-setup is copied from the D7 entityform module.)
+    // Get all workflows, keyed by wid.
+    $types = db_select('workflows', 'w')
+      ->fields('w')
+      ->execute()
+      ->fetchAllAssoc('wid');
+  }
+  catch (PDOException $ex) {
+    watchdog(
+      'workflow',
+      'Failed to retrieve workflow types: %exception',
+      array('%exception' => (string)$ex),
+      WATCHDOG_ERROR);
+
+    $types = array();
+  }
+
+  // Build an array of bundles.
+  foreach ($types as $type => $info) {
+    $entities['WorkflowTransition']['bundles'][$type] = array(
+      'label' => $info->label,
+      'admin' => array(
+        'path' => 'admin/config/workflow/workflow/manage/%workflow',
+        'real path' => 'admin/config/workflow/workflow/manage/' . $type,
+        'bundle argument' => 5,
+//        'access callback' => 'workflow_access',
+      ),
+    );
+  }
+
+  $entities['WorkflowScheduledTransition'] = array(
+    'label' => t('Workflow scheduled transition'),
+    'entity class' => 'WorkflowScheduledTransition',
+    'controller class' => 'WorkflowTransitionController',
+    'metadata controller class' => 'EntityDefaultMetadataController',
+    'views controller class' => 'EntityDefaultViewsController',
+    'rules controller class' => 'EntityDefaultRulesController',
+    'features controller class' => FALSE,
+    'base table' => 'workflow_scheduled_transition',
+    'entity keys' => array(
+      'id' => 'tid',
+    ),
+    'label callback' => 'entity_class_label',
+    'module' => 'workflow',
+  );
+
+  return $entities;
+}
+
+
+/**
+ * Does NOT implement hook_entity_property_info().
+ *
+ * By NOT implementing this hook, but using hook_entity_property_info_alter(),
+ * the system itself will generate the most viable options.
+ * They can then be tweaked with the _alter() function.
+ *
+ * @see https://www.drupal.org/node/1208874
+ */
+// function workflow_entity_property_info() {
+// $info = array();
+// $return $info;
+// }
+
+/**
+ * Implements hook_entity_property_info_alter().
+ *
+ * N.B. Keep the following functions aligned when changing properties:
+ * - workflow_tokens()
+ * - workflow_entity_property_info_alter()
+ * - workflow_views_views_data_alter()
+ */
+function workflow_entity_property_info_alter(&$info) {
+
+  // Properties for Entites. @todo wrapper: not only nodes.
+//  $info['node']['properties']['workflows'] = array(
+//    'type' => 'list<Workflow>',
+//    'label' => t("Workflow"),
+//    'description' => t("The workflow of the entity."),
+//    'getter callback' => '_workflow_metadata_workflow_get_properties',
+//    'entity token' => FALSE,
+//  );
+
+  // Properties for Workflow.
+  $info['Workflow']['properties']['wid']['label'] = 'Workflow ID';
+  $info['Workflow']['properties']['options']['label'] = t('Workflow options');
+  $info['Workflow']['properties']['options']['getter callback'] = '_workflow_metadata_workflow_get_properties';
+  $info['Workflow']['properties']['states'] = array(
+    'type' => 'list<WorkflowState>',
+    'label' => 'States',
+    'description' => 'States of the Workflow.',
+    'getter callback' => '_workflow_metadata_workflow_get_properties',
+    // 'options list' => 'entity_metadata_user_roles',
+    // 'required' => TRUE/FALSE,
+    // 'entity views field' => TRUE/FALSE,
+    // 'entity token' => TRUE/FALSE,
+  );
+  $info['Workflow']['properties']['transitions'] = array(
+    'type' => 'list<WorkflowConfigTransition>',
+    'label' => 'Transitions',
+    'description' => 'Transitions of the Workflow.',
+    'getter callback' => '_workflow_metadata_workflow_get_properties',
+  );
+  unset($info['Workflow']['properties']['tab_roles']['description']);
+  if (!isset($info['Workflow']['properties']['tab_roles']) || !is_array($info['Workflow']['properties']['tab_roles'])) {
+    $info['Workflow']['properties']['tab_roles'] = array();
+  }
+  $info['Workflow']['properties']['tab_roles'] += array(
+    'type' => 'list<integer>',
+    'label' => t("User roles"),
+    'description' => t("The roles that can access the Workflow History tab."),
+    'getter callback' => '_workflow_metadata_workflow_get_properties',
+  );
+
+  // Properties for WorkflowState.
+  $info['WorkflowState']['properties']['wid']['type'] = 'Workflow';
+  //
+  $info['WorkflowState']['properties']['label'] = $info['WorkflowState']['properties']['state'];
+  $info['WorkflowState']['properties']['label']['label'] = t("Label");
+  $info['WorkflowState']['properties']['label']['description'] = t("The label of the state.");
+  $info['WorkflowState']['properties']['label']['required'] = TRUE;
+  unset($info['WorkflowState']['properties']['state']);
+
+  // Properties for WorkflowConfigTransition.
+  $info['WorkflowConfigTransition']['properties']['workflow'] = array(
+    'type' => 'Workflow',
+    'label' => t("Workflow"),
+    'description' => t("The workflow of the transition."),
+    'getter callback' => '_workflow_metadata_workflow_get_properties',
+    'entity token' => FALSE,
+  );
+  $info['WorkflowConfigTransition']['properties']['wid'] = array(
+    'type' => 'integer',
+    'label' => t("Workflow  ID"),
+    'description' => t("The workflow ID of the transition."),
+    'getter callback' => '_workflow_metadata_workflow_get_properties',
+    'entity token' => FALSE,
+  );
+  // @todo: Unify ID's to old_sid, new_sid.
+  $info['WorkflowConfigTransition']['properties']['sid']['description'] = 'The ID of old state.';
+  $info['WorkflowConfigTransition']['properties']['target_sid']['description'] = 'The ID of new state.';
+
+  // Unify objects to old_state, new_state.
+  $info['WorkflowConfigTransition']['properties']['old_state'] = array(
+    'type' => 'WorkflowState',
+    'label' => t('Old state'),
+    'schema field' => 'sid',
+    'description' => t("The old state."),
+    'getter callback' => '_workflow_metadata_workflow_get_properties',
+  );
+  $info['WorkflowConfigTransition']['properties']['new_state'] = array(
+    'type' => 'WorkflowState',
+    'label' => t('New state'),
+    'schema field' => 'target_sid',
+    'description' => t("The new state."),
+    'getter callback' => '_workflow_metadata_workflow_get_properties',
+  );
+  $info['WorkflowConfigTransition']['properties']['roles']['type'] = 'list<integer>';
+  $info['WorkflowConfigTransition']['properties']['roles']['description'] = t("The roles that may execute the transition.");
+
+  // Properties for WorkflowTransition.
+  $info['WorkflowTransition']['properties']['label'] = $info['WorkflowConfigTransition']['properties']['label'];
+  $info['WorkflowTransition']['properties']['workflow'] = $info['WorkflowConfigTransition']['properties']['workflow'];
+  $info['WorkflowTransition']['properties']['wid'] = $info['WorkflowConfigTransition']['properties']['wid'];
+  $info['WorkflowTransition']['properties']['hid']['label'] = 'Transition ID';
+
+  // @todo: Unify ID's to old_sid, new_sid.
+  $info['WorkflowTransition']['properties']['old_sid']['description'] = 'The ID of old state.';
+  $info['WorkflowTransition']['properties']['sid']['description'] = 'The ID of new state.';
+  // Unify objects to old_state, new_state.
+  $info['WorkflowTransition']['properties']['old_state'] = $info['WorkflowConfigTransition']['properties']['old_state'];
+  $info['WorkflowTransition']['properties']['new_state'] = $info['WorkflowConfigTransition']['properties']['new_state'];
+
+  $info['WorkflowTransition']['properties']['user'] = array(
+    'type' => 'user',
+    'label' => t('User'),
+    'schema field' => 'uid',
+    'description' => t('The user who triggered the transition.'),
+  );
+
+  unset($info['WorkflowTransition']['properties']['stamp']);
+  $info['WorkflowTransition']['properties']['timestamp'] = array(
+    'type' => 'date',
+    'label' => t('Timestamp'),
+    'schema field' => 'stamp',
+    'description' => t('The date, time the transition was executed.'),
+  );
+
+  $info['WorkflowTransition']['properties']['entity'] = array(
+    'type' => 'entity',
+    'label' => t('Entity'),
+    'description' => t("The Entity that this state change applies to."),
+    'getter callback' => '_workflow_metadata_workflow_get_properties',
+  );
+
+  // Properties for WorkflowScheduledTransition.
+  $info['WorkflowScheduledTransition']['properties']['scheduled']['type'] = 'date';
+  $info['WorkflowScheduledTransition']['properties']['workflow'] = $info['WorkflowTransition']['properties']['workflow'];
+  // @todo: Unify ID's to old_sid, new_sid.
+  $info['WorkflowScheduledTransition']['properties']['old_sid']['description'] = 'The ID of old state.';
+  $info['WorkflowScheduledTransition']['properties']['sid']['description'] = 'The ID of new state.';
+  // Unify objects to old_state, new_state.
+  $info['WorkflowScheduledTransition']['properties']['old_state'] = $info['WorkflowTransition']['properties']['old_state'];
+  $info['WorkflowScheduledTransition']['properties']['old_state']['schema field'] = 'old_sid';
+  $info['WorkflowScheduledTransition']['properties']['new_state'] = $info['WorkflowTransition']['properties']['new_state'];
+  $info['WorkflowScheduledTransition']['properties']['new_state']['schema field'] = 'new_sid';
+  $info['WorkflowScheduledTransition']['properties']['user'] = $info['WorkflowTransition']['properties']['user'];
+}
+
+/**
+ * Getter callback for Workflow defined in hook_entity_property_info_alter.
+ */
+function _workflow_metadata_workflow_get_properties($entity, array $options, $name, $entity_type, $property) {
+  switch ($name) {
+//    // The workflows of a normal entity.
+//    case 'workflows':
+//      $workflow = _workflow_get_workflow_creation_sid($entity_type, $entity, $field_name);
+//      return $workflow;
+//      return $entity->getWorkflow();
+
+    // The workflows of a Workflow entity.
+    case 'workflow':
+      return $entity->getWorkflow();
+
+    case 'states':
+      return $entity->getStates();
+
+    case 'transitions':
+      // @todo: for some reason, getTransitions() gives no result.
+      return $entity->getTransitions();
+
+    case 'old_state':
+    case 'old-state':
+      return $entity->getOldState();
+
+    case 'new_state':
+    case 'new-state':
+      return $entity->getNewState();
+
+    case 'tab_roles':
+      // @todo: for some reason, Tab_roles gives no result.
+      // Code copied from 'user' entity.
+      return isset($entity->tab_roles) ? array_keys($entity->tab_roles) : array();
+
+    case 'langcode':
+      // Gets the language code of a Workflow Field, hence its State, Transitions.
+      // '$property' is $field_name.
+      $langcode = LANGUAGE_NONE;
+      $wrapper = entity_metadata_wrapper($entity_type, $entity);
+
+      if (!$property || !method_exists($wrapper, $property)) {
+        // Workflow_node. Translations are not supported.
+      }
+      else {
+        // Get language code for a field or property.
+        // getPropertyLanguage() may return NULL if no language is set,
+        // or may not exist on properties.
+
+        if (isset($wrapper->{$property}) && method_exists($wrapper->{$property}, 'getPropertyLanguage')) {
+          if (!$langcode = $wrapper->{$property}->getPropertyLanguage()) {
+            $langcode = LANGUAGE_NONE;
+          }
+        }
+      }
+      return $langcode;
+
+    case 'entity':
+      $entity_wrapper = entity_metadata_wrapper($entity->entity_type, $entity->nid);
+      return $entity_wrapper;
+
+    // The following properties need more love. Also test their tokens!
+    case 'options':
+      return 'n/a';
+  }
+}
+
+
+/**
+ * Implements hook_field_extra_fields().
+ *
+ * We do not use 'extra fields controller class', which only adds 'display'
+ * fields, not 'form' fields. Use hook_field_extra_fields instead.
+ *
+ * hook_field_extra_fields() is invoked by _field_extra_fields_pre_render(), which is a pre-render function used by field_attach_form(), and field_attach_view().
+ * @see https://api.drupal.org/api/drupal/modules!field!field.api.php/function/hook_field_extra_fields/7
+ * @see http://drupal.stackexchange.com/questions/10527/how-should-i-use-hook-field-extra-fields
+ *
+ */
+function workflow_field_extra_fields() {
+  $extra = array();
+
+  $entity_type = 'WorkflowTransition';
+
+  $info = entity_get_info($entity_type);
+  foreach ($info['bundles'] as $bundle => $bundle_info) {
+    $extra[$entity_type][$bundle] = array(
+      'form' => array(
+        'workflow_sid' => array(
+          'widget-type' => 'select',
+          'label' => t('State Id'),
+          'description' => t('Target state Id'),
+          'weight' => -4,
+          // 'edit' => 'test', // (optional) String containing markup (normally a link) used as the element's 'edit' operation in the administration interface. Only for 'form' context.
+          // 'delete' => 'test', // (optional) String containing markup (normally a link) used as the element's 'delete' operation in the administration interface. Only for 'form' context.
+        ),
+        'scheduled_container' => array(
+          'label' => t('Scheduling'),
+          'description' => t('Schedule checkbox & date/time'),
+          'weight' => -4,
+        ),
+        'workflow_comment' => array(
+          'label' => t('Comment'),
+          'description' => t('Comment text area'),
+          'weight' => -4,
+        ),
+      ),
+      'display' => array(
+      )
+    );
+  }
+
+  return $extra;
+}
+
+/**
+ * Entity loader for Workflow.
+ *
+ * Also used as Menu wild card loader {wildcard_name}_load for '%workflow'.
+ *
+ * @see http://www.phpgainers.com/content/creating-menu-wildcard-loader-function-drupal-7
+ * @todo D8: deprecated in favour of workflow_load_single(), not needed for menu.
+ *
+ * $id can be numeric ID ('wid') or machine name ('name').
+ * Caveat: this only works for entities with EntityAPIControllerExportable. #1741956
+ */
+function workflow_load($id) {
+  // Some Admin UI menu page loaders pass the $wid as string, not int.
+  // @see workflow_admin_ui_edit_form_validate().
+  $workflow = entity_load_single('Workflow', $id);
+  return $workflow;
+}
+
+function workflow_load_by_name($name) {
+  $workflows = entity_load_multiple_by_name('Workflow', array($name));
+  return reset($workflows);
+}
+
+function workflow_load_multiple_by_name($names = FALSE) {
+  $workflows = entity_load_multiple_by_name('Workflow', $names);
+  return $workflows;
+}
+
+function workflow_load_single($id) {
+  return entity_load_single('Workflow', $id);
+}
+
+function workflow_load_multiple($ids = FALSE, $reset = FALSE) {
+  return entity_load('Workflow', $ids, array(), $reset);
+}
+
+function workflow_create($name) {
+  // @todo: avoid double names in db-table, to get rid of this line of code.
+  $workflow = workflow_load_by_name($name);
+  if (!$workflow) {
+    $workflow = entity_create('Workflow', array('name' => $name));
+  }
+  return $workflow;
+}
+
+
+/**
+ * Helper function, to get the label of a given workflow.
+ *
+ * @see workflow_get_sid_label($sid)
+ * @see workflow_get_wid_label($wid)
+ */
+function workflow_label($workflow) {
+  if ($workflow === FALSE) {
+    $output = t('No workflow');
+  }
+  elseif ($workflow) {
+    $output = check_plain(t($workflow->label()));
+  }
+  else {
+    // E.g., NULL.
+    $output = t('Unknown workflow');
+  }
+  return $output;
+}
+
+/**
+ * Reset the Workflow when States, Transitions have been changed.
+ */
+function workflow_reset_cache($wid) {
+  $ids = array($wid);
+  entity_get_controller('Workflow')->resetCache($ids);
+}
+
+/**
+ * CRUD for WorkflowState.
+ */
+function workflow_state_load($sid) {
+  return WorkflowState::load($sid);
+}
+
+function workflow_state_load_single($sid) {
+  return WorkflowState::load($sid);
+}
+
+function workflow_state_load_multiple($wid = 0, $reset = FALSE) {
+  return WorkflowState::getStates($wid, $reset);
+}
+
+function workflow_state_load_by_name($name, $wid) {
+  return WorkflowState::loadByName($name, $wid);
+}
+
+/**
+ * Load WorkflowTransitions, most recent first.
+ *
+ * @param string $field_name
+ *   Optional. Can be NULL, if you want to load any field.
+ *
+ * @deprecated: workflow_get_workflow_node_history_by_nid() --> workflow_transition_load_single()
+ * @deprecated: workflow_get_recent_node_history() --> workflow_transition_load_single()
+ */
+function workflow_transition_load_multiple($entity_type, array $entity_ids, $field_name = '', $limit = NULL, $langcode = '') {
+  return WorkflowTransition::loadMultiple($entity_type, $entity_ids, $field_name, $limit, $langcode);
+}
+
+/**
+ * Load WorkflowTransitions, most recent first.
+ *
+ * @return WorkflowTransition
+ *   object representing one row from the {workflow_node_history} table.
+ */
+function workflow_transition_load_single($entity_type, $entity_id, $field_name = '', $langcode = '') {
+  $limit = 1;
+  if ($transitions = workflow_transition_load_multiple($entity_type, array($entity_id), $field_name, $limit, $langcode)) {
+    return reset($transitions);
+  }
+  return NULL;
+}
+
+/**
+ * Load function belonging to the menu option 'workflow-comment/%'.
+ *
+ * Maps to this function just like 'node/%node' maps to node_load().
+ *
+ * @param int $hid
+ *   The ID of the workflow state transition record to load.
+ *
+ * @return WorkflowTransition
+ *   object representing one row from the {workflow_node_history} table.
+ */
+function workflow_transition_load($hid) {
+  return entity_load_single('WorkflowTransition', $hid);
+}

+ 198 - 222
sites/all/modules/contrib/admin/workflow/workflow.features.inc

@@ -2,269 +2,245 @@
 
 /**
  * @file
- * Intergrates workflow with features.
+ * Provides Features integration for Workflow using the CRUD API.
+ *
+ * As you will notice this file will only handle the <export> of Worflows,
+ * including states and transitions. The <import> is handeled magically,
+ * and all modifications are done in function Workflow::save().
  */
 
 define('WORKFLOW_FEATURES_AUTHOR_NAME', 'workflow_features_author_name');
 
-/**
- * Workflows are a **faux-exportable** component.
- */
+// Even if workflow Node is not enabled, Features may use Node API's type_maps.
+require_once dirname(__FILE__) . '/workflow.node.type_map.inc';
 
 /**
- * Implements hook_features_export().
+ * Default controller handling features integration.
  */
-function workflow_features_export($data, &$export, $module_name = '') {
-  // fontyourface_default_fonts integration is provided by Features.
-  $export['dependencies']['features'] = 'features';
-  $export['dependencies']['workflow'] = 'workflow';
-  foreach (workflow_get_workflows() as $workflow) {
-    if (in_array($workflow->name, $data)) {
-      $export['features']['workflow'][$workflow->name] = $workflow->name;
+class WorkflowFeaturesController extends EntityDefaultFeaturesController {
+
+  /**
+   * Generates the result for hook_features_export().
+   */
+  public function export($data, &$export, $module_name = '') {
+    $pipe = parent::export($data, $export, $module_name);
+
+    foreach ($data as $workflow_name) {
+      if ($workflow = workflow_load_by_name($workflow_name)) {
+        // Add dependency on workflow_node.
+        if (count($workflow->getTypeMap())) {
+          $export['dependencies']['workflownode'] = 'workflownode';
+        }
+        // Add dependency on workflow_field.
+        $export['features']['Workflow'][$workflow_name] = $workflow_name;
+      }
     }
+
+    return $pipe;
   }
-  return $export;
-}
 
-/**
- * Implements hook_features_export_render().
- */
-function workflow_features_export_render($module, $data) {
-  $translatables = $code = array();
-  $code[] = '  $workflows = array();';
-  $code[] = '';
-
-  $workflows = workflow_get_workflows();
-  foreach ($data as $name) {
-    if ($workflow = workflow_get_workflows_full_object($name)) {
-      unset($workflow->wid);
-      $workflow_export = features_var_export($workflow, '  ');
-      $workflow_identifier = features_var_export($workflow->name);
-      $code[] = "  // Exported workflow: $name";
-      $code[] = "  \$workflows[{$workflow_identifier}] = {$workflow_export};";
-      $code[] = "";
+  /**
+   * Generates the result for hook_features_export_render().
+   *
+   * This is a copy of the parent, adding 'system_roles'.
+   * The workflow is imported in the target system with Workflow::save().
+   */
+  public function export_render($module, $data, $export = NULL) {
+    $translatables = $code = array();
+    $code[] = '  $workflows = array();';
+    $code[] = '';
+
+    foreach ($data as $identifier) {
+      // Clone workflow to make sure changes are not propagated to original.
+      if ($workflow = entity_load_single($this->type, $identifier)) {
+        $this->export_render_workflow($workflow, $identifier, $code);
+      }
     }
-  }
 
-  $code[] = '  return $workflows;';
-  $code = implode("\n", $code);
-  return array('workflow_default_workflows' => $code);
-}
+    $code[] = '  return $workflows;';
+    $code = implode("\n", $code);
 
-/**
- * Implements hook_features_export_options().
- */
-function workflow_features_export_options() {
-  $workflows = array();
-  foreach (workflow_get_workflows() as $workflow) {
-    $workflows[$workflow->name] = $workflow->name;
-  }
-  return $workflows;
-}
+    $hook = isset($this->info['export']['default hook']) ? $this->info['export']['default hook'] : 'default_' . $this->type;
 
-/**
- * Implements hook_features_revert().
- */
-function workflow_features_revert($module) {
-  // Including the features inc to make sure this func is available during install of a Features module.
-  module_load_include('inc', 'features', 'features.export');
-  foreach (features_get_default('workflow', $module) as $key => $workflow) {
-    workflow_update_workflows_full_object($workflow);
+    return array($hook => $code);
   }
-}
-
-/**
- * Implements hook_features_export_rebuild().
- */
-function workflow_features_export_rebuild($module) {
-  workflow_features_revert($module);
-}
 
-/**
- * CRUD style functions below.
- */
-
-/**
- * For use by CRUD only, save everything from the CRUD formed object.
- *
- * @see workflow_get_workflows_full_object
- *
- * @param $workflow
- *   A fully loaded workflow object to save the states of.
- *
- * @return
- *   Returns whether the workflow was saved fully.
- */
-function workflow_update_workflows_full_object($workflow) {
-  $workflow = (object) $workflow;
-  // Given a workflow in the format returned from export.
-  // First we grab the states, transitions and node_maps out.
-  $states = isset($workflow->states) ? $workflow->states : array();
-  $transitions = isset($workflow->transitions) ? $workflow->transitions : array();
-  $node_types = isset($workflow->node_types) ? $workflow->node_types : array();
-  unset($workflow->states, $workflow->transitions, $workflow->node_types);
-
-  // Then make a workflow so we can track by wid.
-  if ($orig_workflow = workflow_get_workflows_by_name($workflow->name)) {
-    $workflow->wid = $orig_workflow->wid;
+  /**
+   * Renders the provided workflow into export code.
+   *
+   * @param Workflow $workflow
+   *   The workflow to export.
+   * @param string $identifier
+   *   The unique machine name for the workflow in the export.
+   * @param array $code
+   *   A reference to the export code array that will receive the output.
+   */
+  protected function export_render_workflow(Workflow $workflow, $identifier, array &$code) {
+    // Make sure data is not copied to the database.
+    $workflow = clone $workflow;
+
+    $this->sanitize_workflow_for_export($workflow);
+
+    // Make sure to escape the characters \ and '.
+    // The following method has the advantage, that you can export with
+    // features,
+    // and later import without enabling Features in the target system.
+    $workflow_export = addcslashes(entity_export($this->type, $workflow, '  '), '\\\'');
+    $workflow_identifier = features_var_export($identifier);
+
+    $code[] = "  // Exported workflow: {$workflow_identifier}";
+    $code[] = "  \$workflows[{$workflow_identifier}] = entity_import('{$this->type}', '" . $workflow_export . "');";
+    $code[] = ''; // Blank line
   }
 
-  workflow_update_workflows($workflow, FALSE);
-
-  // Cancel out if workflow failed to save.
-  if (!isset($workflow->wid) || empty($workflow->wid)) {
-    return FALSE;
+  /**
+   * Prepares the provided workflow for export.
+   *
+   * Removes serial IDs and replaces them with machine names.
+   *
+   * @param Workflow $workflow
+   *   The workflow to sanitize. The contents of this object are modified directly.
+   */
+  protected function sanitize_workflow_for_export(Workflow $workflow) {
+    // Eliminate serial IDs in exports to prevent "Overridden" status.
+    // We use machine names instead.
+    unset($workflow->wid);
+
+    // Add roles to translate role IDs on target system.
+    $permission = NULL;
+
+    $workflow->system_roles = workflow_get_roles($permission);
+
+    $sid_to_name_map = $this->pack_states($workflow);
+    $this->pack_transitions($workflow, $sid_to_name_map);
   }
 
-  // Workflow is now a fully vetted workflow object. We have NOT created a creation state with this.
-  // Then make states, marking state name to state sid.
-  $active_states = array();
-  foreach ($states as $state) {
-    $state = (object) $state;
-    $state->wid = $workflow->wid;
-    if ($orig_state = reset(workflow_get_workflow_states_by_wid_state($state->wid, $state->state))) {
-      $state->sid = $orig_state->sid;
-    }
-    workflow_update_workflow_states($state);
-    $active_states[$state->state] = $state->sid;
-  }
+  /**
+   * "Packs" the states in the provided workflow into an export-friendly format.
+   *
+   * @param Workflow $workflow
+   *   The workflow to pack. The contents of this object are modified directly.
+   *
+   * @return array
+   *   A map of the old state IDs to their new machine names.
+   */
+  protected function pack_states(Workflow $workflow) {
+    $named_states    = array();
+    $sid_to_name_map = array();
+
+    foreach ($workflow->states as $state) {
+      /* @var WorkflowState $state */
+      $name = $state->getName();
+
+      $sid_to_name_map[$state->sid] = $name;
+
+      // Eliminate serial IDs in exports to prevent "Overridden" status.
+      // We use machine names instead.
+      unset($state->sid);
+      unset($state->wid);
 
-  // Delete any states *not* in our original construction.
-  foreach (workflow_get_workflow_states_by_wid($workflow->wid) as $state) {
-    if (!in_array($state->sid, $active_states)) {
-      workflow_delete_workflow_states_by_sid($state->sid);
+      $named_states[$name] = $state;
     }
-  }
+    ksort($named_states);
 
-  // Then make transitions with the state mapping.
-  $active_transitions = array();
-  foreach ($transitions as $transition) {
-    $transition = (object) $transition;
-    $transition->sid = $active_states[$transition->state];
-    $transition->target_sid = $active_states[$transition->target_state];
-    // Roles are exported by rolename, so need to translate to RID.
-    $transition->roles = !empty($transition->roles) ? _workflow_roles_to_rids($transition->roles) : '';
-    workflow_update_workflow_transitions($transition);
-    $active_transitions[] = $transition->tid;
-  }
+    // Identify states by machine name.
+    $workflow->states = $named_states;
 
-  // Delete any transitions in our workflow that are *not* in our original construction.
-  foreach (workflow_get_workflow_transitions_by_wid($workflow->wid) as $transition) {
-    if (!in_array($transition->tid, $active_transitions)) {
-      workflow_delete_workflow_transitions_by_tid($transition->tid);
-    }
+    return $sid_to_name_map;
   }
-  // Then add the node_type mapping.
-  foreach ($node_types as $node_type) {
-    $node_type = (object) array(
-      'type' => $node_type,
-      'wid' => $workflow->wid
-    );
-    // Insert, nodes only have one workflow. Insert will delete any prior workflow assoc.
-    workflow_insert_workflow_type_map($node_type);
-  }
-
-  return TRUE;
-}
-
-/**
- * For use by CRUD only, gather everything into the CRUD formed object.
- *
- * @param $name
- *   A string corresponding to a workflow object.
- *
- * @return
- *   A fully loaded workflow object with type and statue mappings.
- */
-function workflow_get_workflows_full_object($name) {
-  if ($workflow = workflow_get_workflows_by_name($name)) {
-    // Now we need to add data to the object for each state, an array of sub-objects.
-    $options = array('status' => 1); // We only want active states for this export.
-    $active_states = array();
-    foreach (workflow_get_workflow_states_by_wid($workflow->wid, $options) as $index => $state) {
-      $active_states[$state->sid] = $state->state;
-      // Remove what we don't need to export.
-      unset($state->sid);
-      unset($state->wid);
-      $workflow->states[] = $state;
-    }
 
-    // Now we need to add data to the export for each transition, an array of sub-objects.
-    // Same goes for transitions, see above re: states.
-    foreach ($active_states as $sid => $state) {
-      // We're going to look everythign up by the start state, not state involved, to avoid dupes.
-      foreach (workflow_get_workflow_transitions_by_sid($sid, $options) as $transition) {
-        // And to get the target state (by name) we need to look it up too.
-        $target_state = workflow_get_workflow_states_by_sid($transition->target_sid);
-        $transition->state = $state;
-        $transition->target_state = $target_state->state;
-        unset($transition->sid, $transition->target_sid);
-        // Translate to role names so works cross install.
-        $transition->roles = !empty($transition->roles) ? _workflow_rids_to_roles($transition->roles) : '';
-        // Remove what we don't need to export.
-        unset($transition->tid);
-        $workflow->transitions[] = $transition;
-      }
+  /**
+   * "Packs" the transitions in the provided workflow into an export-friendly format.
+   *
+   * @param Workflow $workflow
+   *   The workflow to pack. The contents of this object are modified directly.
+   *
+   * @param array $sid_to_name_map
+   *   The map of numeric state IDs to their machine names, for remapping sid
+   *   references.
+   */
+  protected function pack_transitions(Workflow $workflow, array $sid_to_name_map) {
+    $named_transitions = array();
+
+    foreach ($workflow->transitions as $transition) {
+      /* @var WorkflowTransition $transition */
+      $start_name = $sid_to_name_map[$transition->sid];
+      $end_name   = $sid_to_name_map[$transition->target_sid];
+      $new_name = WorkflowConfigTransition::machineName($start_name, $end_name);
+
+      $transition->name        = $new_name;
+      $transition->start_state = $start_name;
+      $transition->end_state   = $end_name;
+
+      // Eliminate serial IDs in exports to prevent "Overridden" status.
+      // We use machine names instead.
+      unset($transition->wid);
+      unset($transition->tid);
+      unset($transition->sid);
+      unset($transition->target_sid);
+
+      $named_transitions[$new_name] = $transition;
     }
+    ksort($named_transitions);
 
-    // Now we need to add data to the export for each type map, an array of sub-objects.
-    // Same goes for node mappings, see above re: states.
-    foreach (workflow_get_workflow_type_map_by_wid($workflow->wid) as $index => $type_map) {
-      $workflow->node_types[] = $type_map->type;
-    }
+    // Identify transitions by new machine name.
+    $workflow->transitions = $named_transitions;
   }
-  return $workflow;
-}
 
-/**
- * Internally cache the user roles as core doesn't.
- */
-function _workflow_user_roles($reset = FALSE) {
-  $roles = &drupal_static(__FUNCTION__);
-  if ($reset || !isset($roles)) {
-    $roles = user_roles();
+  /**
+   * Revert this workflow, either creating the workflow new (if one with the
+   * same machine name is not present), or updating the existing workflow.
+   *
+   * @param string $module
+   *   The name of the feature module whose components should be reverted.
+   */
+  function revert($module = NULL) {
+    // Loads defaults from feature code.
+    $defaults = workflow_get_defaults($module);
+
+    foreach ($defaults as $machine_name => $entity) {
+      workflow_revert($defaults, $machine_name);
+    }
   }
-  return $roles;
 }
 
 /**
- * Translates a role string to RIDs for importing.
+ * Implements hook_features_COMPONENT_alter().
  *
- * @param $role_string
- *   A string of roles or fake 'author' role.
- *
- * @return
- *   A string of RIDs seperated by commas.
+ * Adds the corresponding Workflow to the WorkflowField.
  */
-function _workflow_roles_to_rids($role_string) {
-  $roles = _workflow_user_roles();
-  $rid_array = array();
-  foreach (explode(',', $role_string) as $role_name) {
-    if ($role_name === WORKFLOW_FEATURES_AUTHOR_NAME) {
-      $rid_array[] = 'author';
-    }
-    elseif ($role_name && in_array($role_name, $roles)) {
-      $rid_array[] = array_search($role_name, $roles);
+function workflow_features_pipe_field_base_alter(&$pipe, $data, $export) {
+  if (!empty($data)) {
+    foreach ($data as $field_name) {
+      // $info = field_info_field($field_name);
+      $field = _workflow_info_field($field_name);
+
+      if ($field['type'] == 'workflow') {
+        // $field['settings']['wid'] can be numeric or named.
+        $workflow = workflow_load_single($field['settings']['wid']);
+
+        // Fields might reference missing workflows.
+        if (!empty($workflow)) {
+          $pipe['Workflow'][] = $workflow->name;
+        }
+      }
     }
   }
-  return implode(',', $rid_array);
 }
 
 /**
- * Translates a string of rids to role names for exporting.
- *
- * @param $rid_string
- *   A string of rids or fake 'author' role.
+ * Implements hook_features_api_alter().
  *
- * @return
- *   A string of role names seperated by commas.
+ * Ensures Workflow always fires last during rebuild, to ensure that roles
+ * referenced by workflows to be loaded-in when features contain roles.
  */
-function _workflow_rids_to_roles($rid_string) {
-  $roles = _workflow_user_roles();
-  $rid_array = explode(',', $rid_string);
-  // There may be a role named 'author', so make 'author' distinct.
-  $return = in_array('author', $rid_array) ? WORKFLOW_FEATURES_AUTHOR_NAME . ',' : '';
-  // Translate RIDs to rolenames.
-  $return .= implode(',', array_intersect_key($roles, array_flip($rid_array)));
-  return trim($return, ',');
+function workflow_features_api_alter(array &$components) {
+  // FIXME: Why is Workflow the only features provider with an uppercase component name?
+  $component_name = 'Workflow';
+
+  if (isset($components[$component_name])) {
+    $setting = $components[$component_name];
+    unset($components[$component_name]);
+    $components[$component_name] = $setting;
+  }
 }

+ 298 - 0
sites/all/modules/contrib/admin/workflow/workflow.form.inc

@@ -0,0 +1,298 @@
+<?php
+/**
+ * @file
+ * Contains helper functions for WorkflowTransitionForm.
+ */
+
+/**
+ * Getter/Setter to tell if the action buttons are used.
+ *
+ * @param bool $new_value
+ *   Options. If TRUE/FALSE, the value is set. If NULL, value is only 'getted'.
+ *
+ * @return bool
+ *   Previous value. If TRUE, action buttons must be created.
+ *
+ * @see workflow_form_alter()
+ * @see WorkflowDefaultWidget::formElement()
+ *
+ * Used to save some expensive operations on every form.
+ */
+function _workflow_use_action_buttons($new_value = NULL) {
+  global $_workflow_use_actions_buttons;
+
+  $old_value = $_workflow_use_actions_buttons ? TRUE : FALSE;
+  // Reset value if requested.
+  if ($new_value !== NULL) {
+    $_workflow_use_actions_buttons = $new_value;
+  }
+  return $old_value;
+}
+
+/**
+ * Form builder. Move action buttons next to the 'Save'/'Delete' buttons.
+ *
+ * This is only used if the set the 'options widget' to 'action buttons'.
+ * Do not use with multiple workflows per entity: confusing UX.
+ * ATM this works for:
+ * - Workflow Field: create, edit, view, workflow tab, comment;
+ * - Workflow Node: view, workflow tab;
+ * (For forms with Workflow Node, the form_alter() is AFTER formElement(). )
+ *
+ * @todo: move this to WorkflowTransitionForm::_addActionButtons();
+ */
+function workflow_form_alter(&$form, &$form_state, $form_id) {
+
+  // Use a fast, custom way to check if we need to do this.
+  // @todo: Make this work with multiple workflows per entity.
+  if (!_workflow_use_action_buttons()) {
+    return;
+  }
+
+  // Find the first workflow.
+  // (So this won't work with multiple workflows per entity.)
+  $workflow_form = array();
+  if (isset($form['workflow'])) {
+    // Workflow Node or // Workflow History tab.
+    $workflow_form = &$form['workflow'];
+    $field_info = $workflow_form['workflow_field']['#value'];
+    $field_name = $field_info['field_name'];
+    $langcode = LANGUAGE_NONE;
+
+    // Move non-standard 'Submit workflow' button in hook_node_view, workflow_tab_page, workflow_vbo.
+    if (isset($form['workflow']['actions'])) {
+      $form['actions'] = $form['workflow']['actions'];
+      unset($form['workflow']['actions']);
+    }
+  }
+  else {
+    $field_name = '';
+    foreach (element_children($form) as $key) {
+      if (isset($form[$key]['#language'])) {
+        $langcode = $form[$key]['#language'];
+        if (isset($form[$key][$langcode][0]['workflow'])) {
+          // Reference the Workflow Form, we'll remove the buttons later.
+          $field_name = $key;
+          $langcode = $form[$field_name]['#language'];
+          $workflow_form = &$form[$key][$langcode][0]['workflow'];
+          // Stop looping if found.
+          break;
+        }
+      }
+    }
+  }
+
+  // Quit if there is no Workflow on this page.
+  if (!$workflow_form ) {
+    return;
+  }
+
+  // Quit if there are no Workflow Action buttons.
+  // (If user has only 1 workflow option, there are no Action buttons.)
+  if (count($workflow_form['workflow_sid']['#options']) <= 1) {
+    return;
+  }
+
+  // Find the default submit button and replace with our own action buttons.
+  if (isset($form['actions']['submit'])) {
+    $default_submit_action = $form['actions']['submit'];
+    unset($form['actions']['submit']);
+  }
+  elseif (isset($form['actions']['save'])) {
+    $default_submit_action = $form['actions']['save'];
+    unset($form['actions']['save']);
+  }
+  if (isset($default_submit_action)) {
+    $current_sid = $workflow_form['workflow_sid']['#default_value'];
+    $scheduled = isset($workflow_form['workflow_scheduling']['scheduled']) && $workflow_form['workflow_scheduling']['scheduled']['#default_value'];
+
+    // Get the min weight for our buttons.
+    $option_weight = isset($default_submit_action['#weight']) ? $default_submit_action['#weight'] : 0;
+    $option_weight = $option_weight - count($workflow_form['workflow_sid']['#options']);
+    $min_weight = $option_weight;
+    foreach ($workflow_form['workflow_sid']['#options'] as $sid => $option_name) {
+      // Make the workflow button act exactly like the original submit button.
+
+      $same_state_button = ($sid == $current_sid);
+      // Add target State ID and Field name, to set correct value in validate_buttons callback.
+      $workflow_submit_action = $default_submit_action + array(
+        '#workflow_sid' => $sid,
+        '#workflow_field_name' => $field_name,
+      );
+      // Keep option order. Put current state first.
+      $workflow_submit_action['#weight'] = ($same_state_button) ? $min_weight : ++$option_weight;
+      // Add/Overwrite some other settings.
+      $workflow_submit_action['#access'] = TRUE;
+      $workflow_submit_action['#value'] = $option_name;
+      $workflow_submit_action['#attributes'] = ($same_state_button) ? array('class' => array('form-save-default-button')) : array();
+      //$workflow_submit_action['#executes_submit_callback']  = TRUE;
+      $workflow_submit_action['#attributes']['class'][] = drupal_html_class('workflow_button_' . $option_name);
+      // Append the form's #validate function, or it won't be called upon submit,
+      // because the workflow buttons have its own #validate.
+      if (isset($default_submit_action['#validate'])) {
+        $workflow_submit_action['#validate'] = $default_submit_action['#validate'];
+      }
+      elseif (isset($form['#validate'])) {
+        $workflow_submit_action['#validate'] = $form['#validate'];
+      }
+      $workflow_submit_action['#validate'][] = '_workflow_transition_form_validate_buttons';
+      // Append the submit-buttons's #submit function, or it won't be called upon submit.
+      if (isset($default_submit_action['#submit'])) {
+        $workflow_submit_action['#submit'] = $default_submit_action['#submit'];
+      }
+      elseif (isset($form['#submit'])) {
+        $workflow_submit_action['#submit'] = $form['#submit'];
+      }
+
+      // Hide the same-state button in some cases.
+      // Alternative: use hide($element);
+      if ($same_state_button && !$scheduled) {
+        if (substr( $form['#form_id'], 0, 24) == 'workflow_transition_form') {
+          // Hide same-state-button on the transition-form (that is:
+          // view page or workflow history tab) if there is nothing to do.
+          // However, a Transition may be fieldable.
+          if ($form['workflow']['workflow_comment']['#access'] == FALSE) {
+            $workflow_submit_action['#access'] = FALSE;
+          }
+        }
+        elseif ($form['#id'] == 'comment-form') {
+          // On comment-form, the button must stay, since you can comment to same state.
+        }
+        else {
+          // On a entity edit page, the button must stay.
+        }
+      }
+
+      // Place the button with the other action buttons.
+      $form['actions']['workflow_' . $sid] = $workflow_submit_action;
+    }
+
+    // On Edit page, remove the submit button from the workflow form.
+    unset($workflow_form['actions']);
+  }
+}
+
+/**
+ * Form builder. Allow workflow comment change from menu item/Views link.
+ *
+ * @todo D8: This is used, because the D7 form workflow_transition_form
+ * has the wrong parameters for the EntityUIController.
+ * But changing it breaks backwards compatibility.
+ */
+function workflow_transition_wrapper_form(array $form, array &$form_state, $transition, $op, $form_id) {
+  // Override the default function 'entity_ui_controller_form_<op>'; we're not ready for that yet.
+  $form['#validate'] = array('workflow_transition_form_validate');
+  $form['#submit'] = array('workflow_transition_form_submit');
+
+  // The Workflow transition is provided as such:
+  // $form_state['entity_type'] = 'WorkflowTransition';
+  // $form_state['WorkflowTransition'] = (Object) WorkflowTransition;
+  return workflow_transition_form($form, $form_state, array(), array(), '', NULL);
+}
+
+function workflow_transition_form(array $form, array &$form_state, array $field, $instance, $entity_type, $entity) {
+  if (!isset ($instance['widget']['settings']['submit_function'])) {
+    $instance['widget']['settings']['submit_function'] = 'workflow_transition_form_submit';
+  }
+
+  $transition_form = new WorkflowTransitionForm($field, $instance, $entity_type, $entity);
+  return $transition_form->buildForm($form, $form_state);
+}
+
+/**
+ * Submit callback function for the Workflow Form / DefaultWidget.
+ *
+ * Validate target state and either save a transition immediately or schedule
+ * a transition to be executed later by cron.
+ */
+function workflow_transition_form_validate($form, &$form_state) {
+  // Even if this function is empty, it must exist.
+  // Validate the attached fields.
+  // field_attach_form_validate('WorkflowTransition', $form_state['values'], $form, $form_state);
+
+  // $transition_form = new WorkflowTransitionForm($field, $instance, $entity_type, $entity);
+  // return $transition_form->submitForm($form, $form_state, $items);
+}
+
+/**
+ * Submit callback function for the Workflow Form / DefaultWidget.
+ *
+ * Validate target state and either save a transition immediately or schedule
+ * a transition to be executed later by cron.
+ */
+function workflow_transition_form_submit($form, &$form_state) {
+  // Retrieve the data from the form.
+  if (isset($form_state['values']['workflow_field'])) {
+    // If $entity filled: We are on a Entity View page or Workflow History Tab page.
+    // If $entity empty: We are on an Advanced Action page.
+    $field = $form_state['values']['workflow_field'];
+    $instance = $form_state['values']['workflow_instance'];
+    $entity_type = $form_state['values']['workflow_entity_type'];
+    $entity = $form_state['values']['workflow_entity'];
+
+    $items = array();
+    $transition_form = new WorkflowTransitionForm($field, $instance, $entity_type, $entity);
+    return $transition_form->submitForm($form, $form_state, $items);
+  }
+  else {
+    watchdog('workflow', 'workflow_transition_form_submit() is called with error.');
+    // We are on an Entity/Node/Comment Form page.
+    // We should not be here.
+    return;
+  }
+
+}
+
+/**
+ * Submit callback function for the Workflow Form / DefaultWidget.
+ *
+ * Validate form data for 'time' element.
+ */
+function _workflow_transition_form_element_validate_time($element, &$form_state, $form) {
+  if (!strtotime($element['#value'])) {
+    form_error($element, t('Please enter a valid value for time.'));
+  }
+}
+
+/**
+ * Submit callback function for the Workflow Form / DefaultWidget.
+ *
+ * This is only used when using action buttons in Workflow form.
+ * It sets the new state to proper
+ * element and sets a submit function if needed, making sure the action is
+ * executed, influencing function core/includes/form.inc/form_execute_handlers().
+ * (While constructing the Workflow form, we were not yet aware of the submit
+ * buttons of the complete form. We try to correct this here, without adding
+ * another hook_form_alter. We guess the first button is the Save button.
+ */
+function _workflow_transition_form_validate_buttons($form, &$form_state) {
+  $field_name = $form_state['triggering_element']['#workflow_field_name'];
+  $new_sid = $form_state['triggering_element']['#workflow_sid'];
+
+  $langcode = LANGUAGE_NONE;
+  // Retrieve the data from the form.
+  if (isset($form_state['values']['workflow_field'])) {
+    // We are on a Entity View page or Workflow History Tab page.
+    $entity_type = $form_state['values']['workflow_entity_type'];
+    $entity = $form_state['values']['workflow_entity'];
+
+    $langcode = _workflow_metadata_workflow_get_properties($entity, array(), 'langcode', $entity_type, $field_name);
+  }
+
+  /*
+    if (isset($form_state['triggering_element']['#submit'])) {
+      // We are on a View page or History tab. Try to fix the form_state.
+    else {
+      // We are on a Node/Entity/Comment form. Try to fix the form_state.
+    }
+   */
+
+  if ($field_name) {
+    $form_state['input']['workflow_sid'] = $new_sid;
+    $form_state['values'][$field_name][$langcode][0]['workflow']['workflow_sid'] = $new_sid;
+  }
+  else {
+    $form_state['input']['workflow_sid'] = $new_sid;
+    $form_state['values']['workflow_sid'] = $new_sid;
+  }
+}

+ 20 - 6
sites/all/modules/contrib/admin/workflow/workflow.info

@@ -1,12 +1,26 @@
-name = Workflow
-description = "Allows the creation and assignment of arbitrary workflows to node types."
+name = Workflow API
+description = Workflow API. (Enable Workflow Node or Workflow Field to add arbitrary workflows to entities.)
 package = Workflow
 core = 7.x
-files[] = workflow.pages.inc
 
-; Information added by drupal.org packaging script on 2013-07-04
-version = "7.x-1.2"
+dependencies[] = entity (>7.x-1.5)
+dependencies[] = list
+
+files[] = includes/Entity/Workflow.php
+files[] = includes/Entity/WorkflowInterface.php
+files[] = includes/Entity/WorkflowState.php
+files[] = includes/Entity/WorkflowConfigTransition.php
+files[] = includes/Entity/WorkflowScheduledTransition.php
+files[] = includes/Entity/WorkflowTransition.php
+files[] = includes/Field/WorkflowD7Base.php
+files[] = includes/Field/WorkflowItem.php
+files[] = includes/Field/WorkflowDefaultWidget.php
+files[] = includes/Form/WorkflowTransitionForm.php
+files[] = workflow.features.inc
+
+; Information added by Drupal.org packaging script on 2017-01-15
+version = "7.x-2.9+10-dev"
 core = "7.x"
 project = "workflow"
-datestamp = "1372980654"
+datestamp = "1484510888"
 

+ 481 - 44
sites/all/modules/contrib/admin/workflow/workflow.install

@@ -3,13 +3,21 @@
 /**
  * @file
  * Install, update and uninstall functions for the workflow module.
- *
  */
 
 /**
- * Implements hook_install().
+ * Implements hook_enable().
  */
-function workflow_install() {
+function workflow_enable() {
+  $message = t('Thanks for using Workflow. To maintain workflows, enable the
+    <a href="!url_ui">Workflow UI module</a>. To add a Workflow Field to your
+    entity, enable the <a href="!url_field">Workflow Field module</a>.',
+    array(
+      '!url_ui' => url('admin/config/modules'),
+      '!url_field' => url('admin/config/modules'),
+    )
+  );
+  drupal_set_message($message);
 }
 
 /**
@@ -23,6 +31,56 @@ function workflow_uninstall() {
   }
 }
 
+/**
+ * Implements hook_requirements().
+ *
+ * Let admins know that Workflow is in use.
+ *
+ * @todo: extend workflow_requirements() for use with Workflow Field API.
+ */
+function workflow_requirements($phase) {
+  $requirements = array();
+  switch ($phase) {
+    case 'install':
+      break;
+
+    case 'update':
+      break;
+
+    case 'runtime':
+      // Show info on admin/reports/status.
+      $types = db_query('SELECT wid, type FROM {workflow_type_map} WHERE wid <> 0 ORDER BY type')->fetchAllKeyed();
+      // If there are no types, then just bail.
+      if (count($types) == 0) {
+        return;
+      }
+      // Let's make it look nice.
+      if (count($types) == 1) {
+        $type_list = current($types);
+      }
+      else {
+        $last = array_pop($types);
+        if (count($types) > 2) {
+          $type_list = implode(', ', $types) . ', and ' . $last;
+        }
+        else {
+          $type_list = current($types) . ' and ' . $last;
+        }
+      }
+
+      $t = get_t();
+      $requirements['workflow'] = array(
+        'title' => $t('Workflow'),
+        'value' => $t('Workflow is active on the @types content types.', array('@types' => $type_list)),
+        'severity' => REQUIREMENT_OK,
+      );
+      break;
+
+  }
+
+  return $requirements;
+}
+
 /**
  * Implements hook_schema().
  */
@@ -31,29 +89,54 @@ function workflow_schema() {
     'description' => 'Workflows',
     'fields' => array(
       'wid' => array(
-        'description' => 'The primary identifier for a node.',
+        'description' => 'The primary identifier for a workflow.',
         'type' => 'serial',
-        'not null' => TRUE
+        'not null' => TRUE,
       ),
       'name' => array(
-        'description' => 'The name of the workflow (used as machine name for features intergration).',
+        'description' => 'The machine-readable name of this workflow.',
         'type' => 'varchar',
         'length' => '255',
         'not null' => TRUE,
-        'default' => ''
+        'default' => '',
+      ),
+      'label' => array(
+        'description' => 'The human-readable name of this workflow.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'translatable' => TRUE,
+      ),
+      'status' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        // Set the default to ENTITY_CUSTOM without using the constant as it is
+        // not safe to use it at this point.
+        'default' => 0x01,
+        'size' => 'tiny',
+        'description' => 'The exportable status of the entity.',
+      ),
+      'module' => array(
+        'description' => 'The name of the providing module if the entity has been defined in code.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => FALSE,
       ),
       'tab_roles' => array(
         'description' => 'The role IDs that can access the workflow tabs on node pages.',
         'type' => 'varchar',
-        'length' => '60',
+        'length' => '255',
         'not null' => TRUE,
-        'default' => ''
+        'default' => '',
+        'serialize' => TRUE,
       ),
       'options' => array(
         'description' => 'Additional settings for the workflow.',
         'type' => 'text',
         'size' => 'big',
-        'not null' => FALSE
+        'not null' => FALSE,
+        'serialize' => TRUE,
       ),
     ),
     'primary key' => array('wid'),
@@ -66,7 +149,7 @@ function workflow_schema() {
         'type' => 'varchar',
         'length' => '255',
         'not null' => TRUE,
-        'default' => ''
+        'default' => '',
       ),
       'wid' => array(
         'description' => 'The {workflows}.wid this record affects.',
@@ -74,7 +157,7 @@ function workflow_schema() {
         'unsigned' => TRUE,
         'not null' => TRUE,
         'default' => 0,
-        'disp-width' => '10'
+        'disp-width' => '10',
       ),
     ),
     'indexes' => array(
@@ -86,7 +169,22 @@ function workflow_schema() {
       'tid' => array(
         'description' => 'The primary identifier for a workflow transition.',
         'type' => 'serial',
-        'not null' => TRUE
+        'not null' => TRUE,
+      ),
+      'name' => array(
+        'description' => 'The machine-readable name of this transition.',
+        'type' => 'varchar',
+        'length' => '32',
+        // 'not null' => TRUE,
+        'default' => '',
+      ),
+      'label' => array(
+        'description' => 'The human-readable name of this transition.',
+        'type' => 'varchar',
+        'length' => '128',
+        'not null' => TRUE,
+        'default' => '',
+        'translatable' => TRUE,
       ),
       'sid' => array(
         'description' => 'The {workflow_states}.sid start state.',
@@ -94,7 +192,7 @@ function workflow_schema() {
         'unsigned' => TRUE,
         'not null' => TRUE,
         'default' => 0,
-        'disp-width' => '10'
+        'disp-width' => '10',
       ),
       'target_sid' => array(
         'description' => 'The {workflow_states}.sid target state.',
@@ -102,13 +200,14 @@ function workflow_schema() {
         'unsigned' => TRUE,
         'not null' => TRUE,
         'default' => 0,
-        'disp-width' => '10'
+        'disp-width' => '10',
       ),
       'roles' => array(
         'description' => 'The {role}.sid that a user must have to perform transition.',
         'type' => 'varchar',
         'length' => '255',
         'not null' => FALSE,
+        'serialize' => TRUE,
       ),
     ),
     'primary key' => array('tid'),
@@ -130,14 +229,22 @@ function workflow_schema() {
         'unsigned' => TRUE,
         'not null' => TRUE,
         'default' => 0,
-        'disp-width' => '10'
+        'disp-width' => '10',
+      ),
+      'name' => array(
+        'description' => 'The machine-readable name of this state.',
+        'type' => 'varchar',
+        'length' => '255',
+        // 'not null' => TRUE,
+        'default' => '',
       ),
       'state' => array(
-        'description' => 'The name of the state.',
+        'description' => 'The human-readable name of this state.',
         'type' => 'varchar',
         'length' => '255',
         'not null' => TRUE,
         'default' => '',
+        'translatable' => TRUE,
       ),
       'weight' => array(
         'description' => 'The weight (order) of the state.',
@@ -167,19 +274,52 @@ function workflow_schema() {
     'primary key' => array('sid'),
     'indexes' => array(
       'sysid' => array('sysid'),
-      'wid' => array('wid')
+      'wid' => array('wid'),
     ),
   );
   $schema['workflow_scheduled_transition'] = array(
     'fields' => array(
+      'tid' => array(
+        'description' => 'The unique ID for this record.',
+        'type' => 'serial',
+        'not null' => TRUE,
+      ),
+      'entity_type' => array(
+        'description' => 'The type of entity this transition belongs to.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
       'nid' => array(
-        'description' => 'The {node}.nid this transition is scheduled for.',
+        'description' => 'The entity ID of the object this transition belongs to.',
         'type' => 'int',
         'unsigned' => TRUE,
         'not null' => TRUE,
         'default' => 0,
         'disp-width' => '10',
       ),
+      'field_name' => array(
+        'description' => 'The name of the field the transition relates to.',
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'language' => array(
+        'description' => 'The {languages}.language of the entity.',
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'delta' => array(
+        'description' => 'The sequence number for this data item, used for multi-value fields',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
       'old_sid' => array(
         'description' => 'The {workflow_states}.sid this state starts at.',
         'type' => 'int',
@@ -219,8 +359,10 @@ function workflow_schema() {
         'not null' => FALSE,
       ),
     ),
+    'primary key' => array('tid'),
     'indexes' => array(
-      'nid' => array('nid')
+      'entity_type' => array('entity_type'),
+      'entity_id' => array('entity_type', 'nid'),
     ),
   );
   $schema['workflow_node_history'] = array(
@@ -228,7 +370,14 @@ function workflow_schema() {
       'hid' => array(
         'description' => 'The unique ID for this record.',
         'type' => 'serial',
-        'not null' => TRUE
+        'not null' => TRUE,
+      ),
+      'entity_type' => array(
+        'description' => 'The type of entity this transition belongs to.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
       ),
       'nid' => array(
         'description' => 'The {node}.nid this record is for.',
@@ -238,12 +387,40 @@ function workflow_schema() {
         'default' => 0,
         'disp-width' => '10',
       ),
+      'revision_id' => array(
+        'description' => 'The current version identifier.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => FALSE,
+        'default' => NULL,
+      ),
+      'field_name' => array(
+        'description' => 'The name of the field the transition relates to.',
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'language' => array(
+        'description' => 'The {languages}.language of the entity.',
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'delta' => array(
+        'description' => 'The sequence number for this data item, used for multi-value fields',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
       'old_sid' => array(
         'description' => 'The {workflow_states}.sid this transition started as.',
         'type' => 'int',
         'unsigned' => TRUE,
-        'not null' => TRUE, '
-        default' => 0,
+        'not null' => TRUE,
+        'default' => 0,
         'disp-width' => '10',
       ),
       'sid' => array(
@@ -279,7 +456,8 @@ function workflow_schema() {
     ),
     'primary key' => array('hid'),
     'indexes' => array(
-      'nid' => array('nid', 'sid'),
+      'sid' => array('entity_type', 'nid', 'sid'),
+      'nid' => array('nid'),
     ),
   );
   $schema['workflow_node'] = array(
@@ -333,7 +511,9 @@ function workflow_update_last_removed() {
 }
 
 /**
- * Table update from 6 to 7. Adding a unique key for fields (already held unique in code).
+ * Table update from 6 to 7.
+ *
+ * Adding a unique key for fields (already held unique in code).
  */
 function workflow_update_7000() {
   if (!db_index_exists('workflows', 'name')) {
@@ -344,25 +524,6 @@ function workflow_update_7000() {
   }
 }
 
-/**
- * Initialize all workflows to show watchdog messages.
- */
-function workflow_update_7001() {
-  // Get all workflows.
-  $workflows = workflow_get_workflows();
-  
-  foreach ($workflows as $workflow) {
-    // Add the option.
-    $workflow->options['watchdog_log'] = 1;
-    
-    // Serialize the options array.
-    $workflow->options = serialize($workflow->options);
-    
-    // Update the workflow without creating a creation state.
-    workflow_update_workflows($workflow, FALSE);
-  }
-}
-
 /**
  * Add userid to scheduled transition table.
  */
@@ -377,3 +538,279 @@ function workflow_update_7002() {
     'initial' => 0,
     ));
 }
+
+/**
+ * Add Entity field capabilities to workflow_scheduled_transition table.
+ */
+function workflow_update_7003() {
+  $field = array(
+    'description' => 'The type of entity this transition belongs to.',
+    'type' => 'varchar',
+    'length' => 255,
+    'not null' => TRUE,
+    'default' => '',
+  );
+  if (!db_field_exists('workflow_scheduled_transition', 'entity_type')) {
+    db_add_field('workflow_scheduled_transition', 'entity_type', $field);
+  }
+  if (!db_field_exists('workflow_node_history', 'entity_type')) {
+    db_add_field('workflow_node_history', 'entity_type', $field);
+  }
+
+  $field = array(
+    'description' => 'The name of the field the transition relates to.',
+    'type' => 'varchar',
+    'length' => 32,
+    'not null' => TRUE,
+    'default' => '',
+  );
+  if (!db_field_exists('workflow_scheduled_transition', 'field_name')) {
+    db_add_field('workflow_scheduled_transition', 'field_name', $field);
+  }
+  if (!db_field_exists('workflow_node_history', 'field_name')) {
+    db_add_field('workflow_node_history', 'field_name', $field);
+  }
+
+  $field = array(
+    'description' => 'The {languages}.language of the entity.',
+    'type' => 'varchar',
+    'length' => 32,
+    'not null' => TRUE,
+    'default' => '',
+  );
+  if (!db_field_exists('workflow_scheduled_transition', 'language')) {
+    db_add_field('workflow_scheduled_transition', 'language', $field);
+  }
+  if (!db_field_exists('workflow_node_history', 'language')) {
+    db_add_field('workflow_node_history', 'language', $field);
+  }
+
+  $field = array(
+    'description' => 'The sequence number for this data item, used for multi-value fields',
+    'type' => 'int',
+    'unsigned' => TRUE,
+    'not null' => TRUE,
+    'default' => 0,
+  );
+  if (!db_field_exists('workflow_scheduled_transition', 'delta')) {
+    db_add_field('workflow_scheduled_transition', 'delta', $field);
+  }
+  if (!db_field_exists('workflow_node_history', 'delta')) {
+    db_add_field('workflow_node_history', 'delta', $field);
+  }
+
+  db_drop_index('workflow_scheduled_transition', 'nid');
+  db_drop_index('workflow_scheduled_transition', 'entity_id');
+  db_drop_index('workflow_scheduled_transition', 'entity_type');
+  db_add_index('workflow_scheduled_transition', 'entity_id', array('entity_type', 'nid'));
+  db_add_index('workflow_scheduled_transition', 'entity_type', array('entity_type'));
+
+  db_drop_index('workflow_node_history', 'nid');
+  db_drop_index('workflow_node_history', 'sid');
+  db_add_index('workflow_node_history', 'sid', array('entity_type', 'nid', 'sid'));
+}
+
+/**
+ * Update scheduled state transitions with no association to "node".
+ */
+function workflow_update_7004() {
+  db_update('workflow_scheduled_transition')
+    ->fields(array(
+      'entity_type' => 'node',
+      'language' => LANGUAGE_NONE,
+      'delta' => '0',
+      )
+    )
+    ->execute();
+}
+
+/**
+ * Enable Workflow Node module. See https:\/\/drupal.org\/node\/2122541 .
+ */
+function workflow_update_7005() {
+  module_enable(array('workflownode'));
+}
+
+/**
+ * Set historical records with no associated entity to "node".
+ *
+ * Otherwise, they won't show up in the Workflow tab.
+ */
+function workflow_update_7006() {
+  db_update('workflow_node_history')
+    ->fields(array(
+      'entity_type' => 'node',
+      )
+    )
+    ->condition('entity_type', '')
+    ->execute();
+}
+
+/**
+ * Convert roles to entity-like arrays.
+ */
+function workflow_update_7007() {
+  // For this update, do not use the Workflow API, since some table fields are
+  // not present yet. Also, do not move to the 'floating' hook_update_N().
+
+  $schema = workflow_schema();
+  // Change length from 60 to 255, to create a 'standard' Roles field,
+  // like workflow_transitions-roles.
+  $table = 'workflows';
+  $fields = $schema[$table]['fields'];
+  db_change_field($table, 'tab_roles', 'tab_roles', $fields['tab_roles']);
+
+  // Save field workflows-tab_roles in serialized array (using explode for the last time).
+  $query = "SELECT * FROM {workflows} w ";
+  $result = db_query($query);
+  foreach ($result as $record) {
+
+    // Replace role ID 'author' by '-1'.
+    // Update workflow->tab_roles to serializable array.
+    $roles = $record->tab_roles;
+    // Allow reprocessing this hook, by checking if this is an array.
+    if (!(strpos($roles, 'a:') === 0)) {
+      $roles = str_replace('author', '-1', $roles);
+      $record->tab_roles = empty($roles) ? array() : explode(',', $roles);
+      $num_updated = db_update('workflows')
+        ->fields(array(
+          'tab_roles' => serialize($record->tab_roles),
+        ))
+        ->condition('wid', $record->wid, '=')
+        ->execute();
+    }
+  }
+
+  // Save field workflow_transitions-roles in serialized array (using explode for the last time).
+  // Replace role ID 'author' by '-1'.
+  $query = "SELECT wt.tid, wt.roles FROM {workflow_transitions} wt";
+  $result = db_query($query);
+  foreach ($result as $record) {
+    $roles = $record->roles;
+    // Allow reprocessing this hook, by checking if this is an array.
+    if (!(strpos($roles, 'a:') === 0)) {
+      $roles = str_replace('author', '-1', $roles);
+      $record->roles = empty($roles) ? array() : explode(',', $roles);
+      $num_updated = db_update('workflow_transitions')
+        ->fields(array(
+          'roles' => serialize($record->roles),
+        ))
+        ->condition('tid', $record->tid, '=')
+        ->execute();
+    }
+  }
+}
+
+/**
+ * Add Revision to workflow history table.
+ *
+ * There is no update for current states.
+ */
+// function workflow_update_7008() {
+//   // This is moved to the general hook_update_N().
+// }
+
+
+/**
+ * Remove invalid transitions.
+ */
+function workflow_update_7014() {
+  // Some error in cloning Workflows generated superfluous, invalid Transitions.
+  $num_deleted = db_delete('workflow_transitions')
+    ->condition('sid', 0)
+    ->execute();
+}
+
+/**
+ * Add database fields. Make Workflow entity-aware, exportable.
+ */
+function workflow_update_7015() {
+  $schema = workflow_schema();
+
+  // Update the Workflow table.
+  $table = 'workflows';
+  $fields = $schema[$table]['fields'];
+  if (!db_field_exists($table, 'label')) {
+    db_add_field($table, 'label', $fields['label']);
+  }
+  if (!db_field_exists($table, 'status')) {
+    db_add_field($table, 'status', $fields['status']);
+  }
+  if (!db_field_exists($table, 'module')) {
+    db_add_field($table, 'module', $fields['module']);
+  }
+
+  // Update the WorkflowConfigTransitions table.
+  $table = 'workflow_transitions';
+  $fields = $schema[$table]['fields'];
+  if (!db_field_exists($table, 'label')) {
+    db_add_field($table, 'label', $fields['label']);
+  }
+  if (!db_field_exists($table, 'name')) {
+    db_add_field($table, 'name', $fields['name']);
+  }
+
+  // Update the WorkflowStates table.
+  $table = 'workflow_states';
+  $fields = $schema[$table]['fields'];
+  if (!db_field_exists($table, 'name')) {
+    db_add_field($table, 'name', $fields['name']);
+  }
+
+  // Update the WorkflowHistory table.
+  $table = 'workflow_node_history';
+  $fields = $schema[$table]['fields'];
+  // This is moved from hook_update_7008().
+  if (!db_field_exists($table, 'revision_id')) {
+    db_add_field($table, 'revision_id', $fields['revision_id']);
+  }
+
+  // Load&save workflows, to populate the label.
+  // Do this after all db-updates!!
+  // Do not use workflow_*() functions.
+  foreach (entity_load('Workflow') as $workflow) {
+    $workflow->save();
+    // Load&save workflow states, to populate the state name.
+    foreach ($workflow->getStates(TRUE, TRUE) as $state) {
+      $state->save();
+    }
+  }
+
+  // The update system is going to flush all caches,
+  // including the updated Rules Action, so nothing to do here.
+}
+
+/**
+ * Add an index to {workflow_node_history}.nid.
+ */
+function workflow_update_7016() {
+  if (db_field_exists('workflow_node_history', 'nid') && !db_index_exists('workflow_node_history', 'nid')) {
+    db_add_index('workflow_node_history', 'nid', array('nid'));
+  }
+}
+
+/**
+ * Add a primary key to {workflow_scheduled_transition}.
+ */
+function workflow_update_7017() {
+  // Drop existing primary key.
+  db_drop_primary_key('workflow_scheduled_transition');
+
+  // Add new primary key.
+  $spec = array(
+    'type' => 'serial',
+  );
+  $keys_new =  array(
+    'primary key' => array('tid'),
+  );
+  db_add_field('workflow_scheduled_transition', 'tid', $spec, $keys_new);
+
+  return t('Added tid primary key to {workflow_scheduled_transition}');
+}
+
+/**
+ * Enable the List module, newly added as a dependency.
+ */
+function workflow_update_7200() {
+  module_enable(array('list'));
+}

+ 0 - 0
sites/all/modules/contrib/admin/workflow/workflow.js


File diff suppressed because it is too large
+ 594 - 800
sites/all/modules/contrib/admin/workflow/workflow.module


+ 92 - 0
sites/all/modules/contrib/admin/workflow/workflow.node.type_map.inc

@@ -0,0 +1,92 @@
+<?php
+/**
+ * @file
+ * Node specific functions, remnants of nodeapi.
+ */
+
+/**
+ * Functions related to table workflow_type_map.
+ */
+
+/**
+ * Gets all workflow_type_map.
+ */
+function workflow_get_workflow_type_map() {
+  $results = db_query('SELECT type, wid FROM {workflow_type_map}');
+  return $results->fetchAllKeyed();
+}
+
+/**
+ * Getss workflow_type_map for a type. On no record, FALSE is returned.
+ *
+ * Currently this is a unique result but requests have been made to allow a node to have multiple
+ * workflows. This is trickier than it sounds as a lot of our processing code will have to be
+ * tweaked to account for multiple results.
+ * ALERT: If a node type is *not* mapped to a workflow it will be listed as wid 0.
+ * Hence, we filter out the non-mapped results.
+ *
+ * @see workflow_get_workflows_by_type()
+ */
+function workflow_get_workflow_type_map_by_type($type) {
+  static $map = array();
+  if (!isset($map[$type])) {
+    $results = db_query('SELECT type, wid FROM {workflow_type_map} WHERE type = :type AND wid <> 0',
+      array(':type' => $type));
+    $map[$type] = $results->fetchObject();
+  }
+  return $map[$type];
+}
+
+/**
+ * Given a wid, finds all node types mapped to it.
+ */
+function workflow_get_workflow_type_map_by_wid($wid) {
+  static $map = array();
+  if (!isset($map[$wid])) {
+    $results = db_query('SELECT type, wid FROM {workflow_type_map} WHERE wid = :wid',
+      array(':wid' => $wid));
+    $map[$wid] = $results->fetchAll();
+  }
+  return $map[$wid];
+}
+
+/**
+ * Deletes all type maps.
+ *
+ * @todo: why is this here instead of the admin_ui?
+ */
+function workflow_delete_workflow_type_map_all() {
+  return db_delete('workflow_type_map')->execute();
+}
+
+/**
+ * Given a wid, deletes the map for that workflow.
+ */
+function workflow_delete_workflow_type_map_by_wid($wid) {
+  return db_delete('workflow_type_map')->condition('wid', $wid)->execute();
+}
+
+/**
+ * Given a type, deletes the map for that workflow.
+ */
+function workflow_delete_workflow_type_map_by_type($type) {
+  return db_delete('workflow_type_map')->condition('type', $type)->execute();
+}
+
+/**
+ * Given information, inserts a new workflow_type_map. Returns data by ref. (like node_save).
+ *
+ * @todo: why is this here instead of the admin_ui?
+ */
+function workflow_insert_workflow_type_map($node_type, $wid) {
+  $type_map = (object) array(
+    'type' => $node_type,
+    'wid' => $wid,
+  );
+
+  // Be sure we have a clean insert. There should never be more than one map for a type.
+  workflow_delete_workflow_type_map_by_type($type_map->type);
+  if ($type_map->wid) {
+    drupal_write_record('workflow_type_map', $type_map);
+  }
+}

+ 162 - 171
sites/all/modules/contrib/admin/workflow/workflow.pages.inc

@@ -2,81 +2,137 @@
 /**
  * @file
  * Provide user interface for changing workflow state.
+ *
+ * @todo D8: remove this in favour of View 'Workflow history per entity'.
  */
 
+define('MARK_STATE_IS_DELETED', '*');
+
 /**
  * Menu callback. Display workflow summary of a node.
+ *
+ * N.B. When having multiple workflows per bundle, use Views display
+ *      'Workflow history per entity' instead!
  */
-function workflow_tab_page($node = NULL) {
-  drupal_set_title($node->title);
-  $workflow = workflow_get_workflow_type_map_by_type($node->type);
-  $states = array();
-  $state_system_names = array();
-  foreach (workflow_get_workflow_states() as $data) {
-    $states[$data->sid] = check_plain(t($data->state));
-    $state_system_names[$data->sid] = check_plain($data->state);
+function workflow_tab_page($entity_type, $entity = NULL) {
+  drupal_set_title(entity_label($entity_type, $entity));
+
+  $form = array();
+  $field_name = NULL;
+  $workflow = NULL;
+
+  // Figure out the $entity's bundle and id.
+  list($entity_id, , $entity_bundle) = entity_extract_ids($entity_type, $entity);
+  $entity_id = entity_id($entity_type, $entity);
+
+  // Get the current sid. $field_name is updated with relevant value.
+  $current_sid = workflow_node_current_state($entity, $entity_type, $field_name);
+  $current_state = workflow_state_load_single($current_sid);
+  $workflow = $current_state->getWorkflow();
+
+  // Show the current state and the Workflow form to allow state changing.
+  // N.B. This part is replicated in hook_node_view, workflow_tab_page, workflow_vbo, transition_edit.
+  // @todo: support multiple workflows per entity.
+  // For workflow_tab_page with multiple workflows, use a separate view. See [#2217291].
+  $field = _workflow_info_field($field_name, $workflow);
+  $field_id = $field['id'];
+  $instance = field_info_instance($entity_type, $field_name, $entity_bundle);
+  if (!$field_id) {
+    // This is a Workflow Node workflow. Set widget options as in v7.x-1.2
+    $field['settings']['widget']['comment'] = isset($workflow->options['comment_log_tab']) ? $workflow->options['comment_log_tab'] : 1; // vs. ['comment_log_node'];
+    $field['settings']['widget']['current_status'] = TRUE;
   }
-  $deleted_states = array();
-  $deleted_state_system_names = array();
-  $options = array('status' => 0);
-  foreach (workflow_get_workflow_states($options) as $data) {
-    $deleted_states[$data->sid] = check_plain(t($data->state));
-    $deleted_state_system_names[$data->sid] = check_plain($data->state);
-  }
-  $current = workflow_node_current_state($node);
 
-  // theme_workflow_history_current_state() must run state through check_plain().
-  $current_state = theme('workflow_history_current_state', array('state_name' => $states[$current], 'state_system_name' => $state_system_names[$current], 'sid' => $current));
+  $form_id = implode('_', array('workflow_transition_form', $entity_type, $entity_id, $field_id));
+  $form += drupal_get_form($form_id, $field, $instance, $entity_type, $entity);
 
-  $output = theme('workflow_current_state', array('state' => $states[$current], 'state_system_name' => $state_system_names[$current], 'sid' => $current));
+  $output = drupal_render($form);
 
-  // Show the form to allow state changing.
-  // But only if we are not at the terminal state.
-  $choices = workflow_field_choices($node);
-  if (count($choices) != 0 || count($choices) != 1 || $current != key($choices)) {
-    $form = drupal_get_form('workflow_tab_form', $node, $workflow->wid, $states, $current);
-    $output .= drupal_render($form);
-  }
 
+  // Show the history table.
   $rows = array();
-  foreach (workflow_get_workflow_node_history_by_nid($node->nid) as $history) {
-    if ($history->sid == $current && !isset($deleted_states[$history->sid]) && !isset($current_themed)) {
+  $current_themed = FALSE;
+  $limit = variable_get('workflow_states_per_page', 20);
+  // Get the history for any field_name.
+  foreach (workflow_transition_load_multiple($entity_type, array($entity_id), NULL, $limit) as $history) {
+    $old_state_name = $new_state_name = '';
+
+    $label = $name = '';
+    $new_state = $history->getNewState();
+    if ($new_state) {
+      $name = $new_state->getName();
+      $label = check_plain(t($new_state->label()));
+    }
+
+    if (!$new_state) {
+      // This is an invalid/deleted state.
+      $old_state_name = $label;
+    }
+    elseif ($history->new_sid == $current_sid && $new_state->isActive() && !$current_themed) {
       // Theme the current state differently so it stands out.
-      $state_name = theme('workflow_history_current_state',  array('state_name' => $states[$history->sid], 'state_system_name' => $state_system_names[$history->sid], 'sid' => $history->sid));
+      $new_state_name = theme('workflow_history_current_state', array(
+        'state_name' => $label,
+        'state_system_name' => $name,
+        'sid' => $history->new_sid,
+      ));
       // Make a note that we have themed the current state; other times in the history
       // of this node where the node was in this state do not need to be specially themed.
       $current_themed = TRUE;
     }
-    elseif (isset($deleted_states[$history->sid])) {
+    elseif (!$new_state->isActive()) {
       // The state has been deleted, but we include it in the history.
-      $state_name = theme('workflow_deleted_state', array('state_name' => $deleted_states[$history->sid], 'state_system_name' => $deleted_state_system_names[$history->sid], 'sid' => $history->sid));
+      $new_state_name = theme('workflow_deleted_state', array(
+        'state_name' => $label,
+        'state_system_name' => $name,
+        'sid' => $history->new_sid,
+      ));
       $footer_needed = TRUE;
     }
     else {
       // Regular state.
-      $state_name = check_plain(t($states[$history->sid]));
+      $new_state_name = $label;
     }
-    if (isset($deleted_states[$history->old_sid])) {
-      $old_state_name = theme('workflow_deleted_state', array('state_name' => $deleted_states[$history->old_sid], 'state_system_name' => $deleted_state_system_names[$history->old_sid], 'sid' => $history->old_sid));
-      $footer_needed = TRUE;
+    unset($new_state); // Not needed anymore.
+
+    $label = $name = MARK_STATE_IS_DELETED;
+    $old_state = $history->getOldState();
+    if ($old_state) {
+      $name = $old_state->getName();
+      $label = check_plain(t($old_state->label()));
+    }
+
+    if (!$old_state) {
+      // This is an invalid/deleted state.
+      $old_state_name = $label;
     }
-    elseif (isset($states[$history->old_sid])) {
-      $old_state_name = check_plain(t($states[$history->old_sid]));
+    elseif (!$old_state->isActive()) {
+      $old_state_name = theme('workflow_deleted_state', array(
+        'state_name' => $label,
+        'state_system_name' => $name,
+        'sid' => $history->old_sid,
+      ));
+      $footer_needed = TRUE;
     }
     else {
-      $old_state_name = '*';
+      // Regular state.
+      $old_state_name = $label;
     }
+    unset($old_state); // Not needed anymore.
 
     $variables = array(
-        'history' => $history,
-        'old_sid' => $history->old_sid,
-        'old_state_name' => $old_state_name,
-        'sid' => $history->sid,
-        'uid' => $history->uid,
-        'state_name' => $state_name,
-        );
+      'transition' => $history, // @todo D8: pass this WorkflowTransition as only variable. It contains everything.
+      'extra' => '',
+
+      'history' => $history,  // @todo D8: remove, as this is the same as 'transition'.
+      'old_sid' => $history->old_sid, // @todo D8: remove this redundant property.
+      'sid' => $history->new_sid, // @todo D8: remove this redundant property.
+      'uid' => $history->uid, // @todo D8: remove this redundant property.
+      'old_state_name' => $old_state_name,
+      'state_name' => $new_state_name,
+    );
 
     // Allow other modules to modify the row.
+    // $todo D8: pass only a $transition object.
     drupal_alter('workflow_history', $variables);
 
     $rows[] = theme('workflow_history_table_row', $variables);
@@ -87,16 +143,27 @@ function workflow_tab_page($node = NULL) {
   $last = count($rows) - 1;
   $rows[$last]['class'][] = 'last';
 
+  $header = array(t('Date'), t('Field name'), t('Old State'), t('New State'), t('By'), t('Comment'));
+  $header[] = array('data' => t('Operations'));
+
   // Only display the table if there's anything in it.
   if ($rows) {
-    $output .= theme('workflow_history_table', array('rows' => $rows, 'footer' => !empty($footer_needed)));
-    $output .= theme('pager', array('tags' => variable_get('workflow_states_per_page', 20)));
+    $variables = array(
+      'header' => $header,
+      'rows' => $rows,
+      'footer' => !empty($footer_needed),
+      'entity' => $entity,
+      'entity_type' => $entity_type,
+    );
+
+    $output .= theme('workflow_history_table', $variables);
+    $output .= theme('pager', array('tags' => $limit));
   }
   return $output;
 }
 
-/*
- * Theme one workflow history table row.
+/**
+ * Theme one WorkflowTansition in a workflow history table row.
  *
  * $old_state_name and $state_name must be run through check_plain(t()) prior
  * to calling this theme function.
@@ -105,155 +172,79 @@ function theme_workflow_history_table_row($variables) {
   $row = array();
   $old_state_name = $variables['old_state_name'];
   $state_name = $variables['state_name'];
-  $history = $variables['history'];
-  $account = user_load($variables['uid']);
+  $transition = $variables['transition'];
   $row = array(
     'data' => array(
-      array('data' => format_date($history->stamp), 'class' => array('timestamp')),
+      array('data' => format_date($transition->stamp), 'class' => array('timestamp')),
+      array('data' => $transition->field_name, 'class' => array('field-name')),
       array('data' => $old_state_name, 'class' => array('previous-state-name')),
       array('data' => $state_name, 'class' => array('state-name')),
-      array('data' => theme('username', array('account' => $account)), 'class' => array('user-name')),
-      array('data' => filter_xss($history->comment), 'class' => array('log-comment')),
-      ),
+      array('data' => theme('username', array('account' => $transition->getUser())), 'class' => array('user-name')),
+      array('data' => filter_xss($transition->comment), 'class' => array('log-comment')),
+      $variables['extra'],
+    ),
     'class' => array('workflow_history_row'),
-    );
-
-  if (!empty($variables['extra'])) {
-    $row['data'][] = $variables['extra'];
-  }
+  );
 
   return $row;
 }
 
-/*
+/**
  * Theme entire workflow history table.
  */
 function theme_workflow_history_table($variables) {
+  $header = $variables['header'];
   $rows = $variables['rows'];
   $footer = $variables['footer'];
-  $headers = array(t('Date'), t('Old State'), t('New State'), t('By'), t('Comment'));
-  $output = theme('table', array('header' => $headers, 'rows' => $rows, 'caption' => t('Workflow History')));
+  $entity = $variables['entity'];
+  $entity_type = $variables['entity_type'];
+  $column_field_name = 1;
+  $column_operations = 6;
+
+  // Remove the Operations column if none are added.
+  $empty = TRUE;
+  foreach ($rows as $row) {
+    $empty &= empty($row['data'][$column_operations]);
+  }
+  if ($empty) {
+    foreach ($rows as &$row) {
+      unset($row['data'][$column_operations]);
+      unset($header[$column_operations]);
+    }
+  }
+
+  // Remove the Field name column if only 1 workflow_field exists.
+  if (count(_workflow_info_fields($entity, $entity_type)) < 2) {
+    foreach ($rows as &$row) {
+      unset($row['data'][$column_field_name]);
+      unset($header[$column_field_name]);
+    }
+
+  }
+
+  $output = theme('table', array('header' => $header, 'rows' => $rows, 'caption' => t('Workflow History')));
   if ($footer) {
-    $output .= t('*State is no longer available.');
+    $output .= MARK_STATE_IS_DELETED . ' ' . t('State is no longer available.');
   }
   return $output;
 }
 
 /**
  * Theme the current state in the workflow history table.
+ *
+ * $state_name must be run through check_plain(t()) prior
+ * to calling this theme function.
  */
 function theme_workflow_history_current_state($variables) {
-  return check_plain(t($variables['state_name']));
+  return $variables['state_name'];
 }
 
 /**
  * Theme a deleted state in the workflow history table.
- */
-function theme_workflow_deleted_state($variables) {
-  return check_plain(t($variables['state_name'])) . '*';
-}
-
-/**
- * Form builder. Allow workflow state change and scheduling from workflow tab.
- *
- * @param $node
- *   Node for which workflow information will be displayed.
- * @param $wid
- *   ID of workflow to display.
- * @param $states
- *   Array of states for the workflow.
- * @param $current
- *   Current workflow state of this node.
- * @return
- *   Form definition array.
- */
-function workflow_tab_form($form, $form_state, $node, $wid, $states, $current) {
-  // Tell FAPI where this form is.
-  form_load_include($form_state, 'inc', 'workflow', 'workflow.pages');
-
-  // Let's make sure we should be here.
-  if (workflow_node_tab_access($node) === FALSE) {
-    return;
-  }
-
-  $form['#tab'] = TRUE;
-  $choices = workflow_field_choices($node);
-  $min = ($states[$current] == t('(creation)') ? 1 : 2);
-
-  // Only build form if user has possible target state(s).
-  if (count($choices) >= $min) {
-    $workflow = workflow_get_workflow_type_map_by_type($node->type);
-    $workflow = workflow_get_workflows_by_wid($workflow->wid);
-
-    $form['#wf'] = $workflow;
-    $form['#choices'] = $choices;
-
-    $name = t($workflow->name);
-    $timestamp = '';
-    $comment = '';
-
-    // See if scheduling information is present.
-    if (!empty($node->workflow_scheduled_timestamp) && !empty($node->workflow_scheduled_sid)) {
-      global $user;
-      if (variable_get('configurable_timezones', 1) && $user->uid && drupal_strlen($user->timezone)) {
-        $timezone = $user->timezone;
-      }
-      else {
-        $timezone = variable_get('date_default_timezone', 0);
-      }
-      // The default value should be the upcoming sid.
-      $current = $node->workflow_scheduled_sid;
-      $timestamp = $node->workflow_scheduled_timestamp;
-      $comment = $node->workflow_scheduled_comment;
-    }
-
-    // Include the same form elements here that are included on a
-    // regular node editing page. $form is modified by reference.
-    workflow_node_form($form, $form_state, t('Change !name state', array('!name' => $name)), $name, $current, $choices, $timestamp, $comment);
-
-    $form['node'] = array(
-      '#type' => 'value',
-      '#value' => $node,
-      );
-
-    $form['submit'] = array(
-      '#type' => 'submit',
-      '#value' => t('Update workflow'),
-      );
-  }
-  return $form;
-}
-
-/**
- * Submit handler for the form on the workflow tab.
  *
- * @see workflow_tab_form()
+ * $state_name must be run through check_plain(t()) prior
+ * to calling this theme function.
  */
-function workflow_tab_form_submit($form, &$form_state) {
-  // The entire node object was stashed in the form.
-  $node = $form_state['values']['node'];
-
-  if (isset($form_state['values']['workflow'])) {
-    $node->workflow = $form_state['values']['workflow'];
-    $node->workflow_comment = isset($form_state['values']['workflow_comment']) ?
-      $form_state['values']['workflow_comment'] : '';
-
-    if (!empty($form_state['values']['workflow_scheduled'])) {
-      $node->workflow_scheduled = $form_state['values']['workflow_scheduled'];
-    }
-    if (!empty($form_state['values']['workflow_scheduled_date'])) {
-      $node->workflow_scheduled_date = $form_state['values']['workflow_scheduled_date'];
-    }
-    if (!empty($form_state['values']['workflow_scheduled_hour'])) {
-      $node->workflow_scheduled_hour = $form_state['values']['workflow_scheduled_hour'];
-    }
-    if (!empty($form_state['values']['workflow_scheduled_timezone'])) {
-      $node->workflow_scheduled_timezone = $form_state['values']['workflow_scheduled_timezone'];
-    }
-  }
-  // ALERT: Rules that use node_save to check the node transition are going to be missed if
-  // the tab form is used to check for the change. It is *always* better practice to use
-  // the transition change itself as your value to check for changes with Rules and other
-  // behaviors. Do NOT rely on node_save() to drive transition changes.
-  workflow_transition($node, $node->workflow);
+function theme_workflow_deleted_state($variables) {
+  return $variables['state_name'] . MARK_STATE_IS_DELETED;
 }

+ 587 - 0
sites/all/modules/contrib/admin/workflow/workflow.test.inc

@@ -0,0 +1,587 @@
+<?php
+
+/**
+ * @file
+ * Contains test functions.
+ */
+
+/**
+ * Test functions.
+ *
+ * To test caches, load same object twice in a page (see d.o. issue #1572466).
+ */
+function _workflow_test_entity_workflow_crud() {
+  $ws = workflow_load_multiple();
+  $count1 = count($ws);
+  $workflow = workflow_create('test' . REQUEST_TIME);
+
+  $workflow->save();
+
+  // Test cache: $w3 must be OK, too.
+  $w2 = workflow_load($workflow->wid);
+  $w3 = workflow_load($workflow->wid);
+  if ($w2 != $w3) {
+    // error.
+  }
+
+  // Test Cache: number of workflows must be OK.
+  $ws = workflow_load_multiple();
+  $count2 = count($ws);
+
+  $workflow->delete();
+  $ws = workflow_load_multiple();
+  $count3 = count($ws);
+
+  if ($count1 === $count3 && ($count2 - $count1) == 1) {
+    drupal_set_message(t('workflow->create/save/delete OK'));
+  }
+  else  {
+    drupal_set_message(t('workflow->create/save/delete: error'));
+  }
+
+  $workflow = workflow_create('test' . REQUEST_TIME);
+  $s1 = $workflow->createState('first');
+  $s2 = $workflow->createState('second');
+  $s3 = $workflow->createState('third');
+  $s1->save();
+  $s2->save();
+  $s2->save();
+  $s3->save();
+}
+
+function _workflow_test_entity1() {
+  $workflow = NULL;
+
+  // Create a workflow.
+  dpm('--- Create Workflow ---');
+  dpm($workflow, '--- test workflow_create() pre');
+  $workflow = workflow_create('test' . REQUEST_TIME);
+  dpm($workflow, '--- test workflow_create() pre_save');
+  $workflow->save();
+  dpm($workflow, '--- test workflow_create() post_save');
+
+  // Create States for the workflow.
+  dpm('--- Create States ---');
+  dpm($workflow->getStates(TRUE), '--- test getStates() pre');
+  dpm($workflow, '--- test getStates() pre');
+  $s1 = $workflow->createState('first');
+  dpm($workflow->getStates(TRUE), '--- test getStates() post s1');
+  dpm($workflow, '--- test getStates() post s1');
+  $s2 = $workflow->createState('second');
+  $s3 = $workflow->createState('third');
+//  $s1->save();
+//  $s2->save();
+//  $s3->save();
+  dpm($workflow->getStates(TRUE), '--- test getStates() post');
+  dpm($workflow, '--- test getStates() post');
+  dpm($s3->getWorkflow(), '--- test getStates() post');
+
+  // Create Transitions for the workflow.
+  dpm('--- Create Transitions ---');
+  dpm('--- Create Transition s1->s2 ---');
+  dpm($workflow->getTransitions(), '--- test getTransitions() pre');
+  $roles = array(-1, 5);
+  $t1 = $workflow->createTransition($s1->sid, $s2->sid);
+  $t1->label = 'transitions t1';
+  $t1->roles = $roles;
+  // The transition was initially saved in createState,
+  // but without roles and name.
+  $t1->save();
+  dpm($t1, '-- test show new Transition');
+  dpm($workflow->getTransitions(), '-- test getTransitions() post 1');
+
+  dpm('--- Create Transition s1->s2 ---');
+  dpm($workflow->getTransitions(), '--- test getTransitions() pre');
+  $roles = array(-1, 6);
+  $t2 = $workflow->createTransition($s2->sid, $s3->sid);
+  $t2->label = 'transitions t2';
+  $t2->roles = $roles;
+  // The transition was initially saved in createState,
+  // but without roles and name.
+  $t2->save();
+  dpm($t2, '-- test show new Transition');
+  dpm($workflow->getTransitions(), '-- test getTransitions() post 2');
+
+  dpm('--- Show resulting Workflow ---');
+  dpm($workflow);
+  dpm('todo: Test if workflow, states and transitions are properly shown in Admin UI.');
+  dpm('--- Show resulting Workflow after workflow_load---');
+  // Refresh the workflow, and show again.
+  $workflow2 = workflow_load($workflow->wid);
+  dpm($workflow2);
+
+//  dpm($t->uri());
+//  dpm($t->label());
+
+  // Remove the workflow and its components.
+  dpm('--- Delete resulting Workflow ---');
+  $workflow->delete();
+  dpm($workflow);
+  dpm('todo: Test if workflow, states and transitions are properly deleted.');
+}
+
+function _workflow_test_entity2() {
+  $w = workflow_load('test');
+  dpm($w);
+  dpm($w->getTransitions());
+  return;
+
+  $ts = entity_load('WorkflowConfigTransition', array(6));
+  $t = reset($ts);
+  $t->roles += array(3 => 3);
+  entity_save('WorkflowConfigTransition', $t);
+}
+
+function _workflow_test_entity3() {
+  $workflow = workflow_load_single(1);
+  dpm($workflow->getStates());
+dpm($workflow->loadTransitions());
+//  $t1 = $workflow->createTransition($s1->sid, $s2->sid);
+//  $t1->save();
+//  dpm($t1);
+//  $t2 = $workflow->createTransition('third', 'second');
+//  $t2->save();
+//  dpm($t2);
+//  dpm($t->uri());
+//  dpm($t->label());
+// $workflow->delete();
+
+//  $ts = entity_load('WorkflowConfigTransition', array(6));
+//  $t = reset($ts);
+//  $t->roles += array(3 => 3);
+//  entity_save('WorkflowConfigTransition', $t);
+}
+
+function _workflow_test_entity_allowable() {
+  global $user;
+
+  $workflow = workflow_create('workflow_test_' . REQUEST_TIME);
+  $workflow->save();
+  $s1 = $workflow->createState('to be opened');
+  $s2 = $workflow->createState('open');
+  $s3 = $workflow->createState('closed');
+  $s1->save();
+  $s2->save();
+  $s3->save();
+  $roles = array(1, 2); // anon, auth user.
+  $t1 = $t = $workflow->createTransition(WORKFLOW_CREATION_STATE_NAME, 'to be opened');
+  $t->roles = $roles;
+  $t->save();
+  $t2 = $t = $workflow->createTransition('to be opened', 'open');
+  $t->roles = $roles;
+  $t->save();
+  $t3 = $t = $workflow->createTransition('open', 'closed');
+  $t->roles = $roles;
+DPM($t3);
+  $t->save();
+DPM($t3);
+  $t4 = $t = $workflow->createTransition('closed', 'open');
+  $t->roles = $roles;
+  $t->save();
+
+  dpm($s2->getOptions('', NULL, '', $user, FALSE));
+  // returns TRUE if the role is allowed to do the transition.
+  $rc = $t3->isAllowed($role = 2);
+  dpm($t3);
+  dpm('this result must be TRUE: ' . (int) $rc);
+  $rc = $t3->isAllowed($role = 3);
+  dpm('this result must be FALSE: ' . (int) $rc);
+
+  $rc = $workflow->GetTransitionsBySid($s3->sid);
+  dpm($rc, 'allowed transitions from ' . $s3->label());
+
+  $ts = $workflow->getTransitions();
+  dpm($ts);
+  $ts = $workflow->getTransitionsBySid($s3->sid);
+  dpm($ts);
+
+  $workflow->delete();
+}
+
+function _workflow_test_metadata() {
+
+  // Find the first, arbitrary workflow.
+  $workflows = workflow_load_multiple();
+  $workflow = reset($workflows);
+dpm($workflow);
+
+  // Add a Workflow wrapper;
+  $wrapper = $workflow_wrapper = entity_metadata_wrapper('Workflow', $workflow);
+dpm(t('---- Workflow: showing objects ---'));
+dpm($wrapper);
+
+  // Get properties of the Workflow.
+dpm(t('---- Workflow: showing getters ---'));
+dpm($wrapper->wid->label() . ' => ' . $wrapper->wid->value() );
+dpm($wrapper->name->label() . ' => ' . $wrapper->name->value() );
+dpm($wrapper->label->label() . ' => ' . $wrapper->label->value() );
+dpm($wrapper->status->value() );
+dpm($wrapper->tab_roles->value() );
+//dpm($wrapper->states->value() );
+dpm($wrapper->states->optionsList() );
+//dpm($wrapper->transitions->value() );
+dpm($wrapper->options->optionsList() );
+  // Set properties of the Workflow.
+  //dpm($wrapper->label->set('ssdfdss') );
+  // ...
+
+  // Get properties of States.
+dpm(t('---- start of WorkflowState getters ---'));
+  $states = $workflow->getStates();
+  $state = reset($states);
+  $state_wrapper = entity_metadata_wrapper('WorkflowState', $state);
+dpm($states);
+dpm(  $state_wrapper->sid->value()  );
+dpm(  $state_wrapper->wid->value()  );
+dpm(  $state_wrapper->weight->value()  );
+//dpm(  $state_wrapper->state->value()  );
+dpm(  $state_wrapper->status->value()  );
+
+  //dpm(  $wrapper->author->mail->value()  );
+  //dpm(  $wrapper->title->value(array('sanitize' => TRUE))  );
+
+  // Get the transitions.
+dpm(t('---- start of WorkflowConfigTransition getters ---'));
+  $transitions = $state->getTransitions();
+dpm($transitions);
+  $options = $state->getOptions('', NULL, '', NULL);
+dpm($options);
+  $transition = reset($transitions);
+dpm($transition);
+
+  $transition_wrapper = entity_metadata_wrapper('WorkflowConfigTransition', $transition);
+dpm(  $transition_wrapper  );
+//dpm(  $transition_wrapper->old_sid->value()  );
+dpm(  $transition_wrapper->old_state->value()  );
+dpm(  $transition_wrapper->old_state->value()->sid  );
+
+}
+
+/**
+ * Test all tokens
+ */
+function _workflow_test_tokens($entity_id = 76, $entity_type = 'node', $field_name = NULL) {
+  global $user;
+
+// $entity_id = 76;
+
+  if ($entity_id) {
+    $node = entity_load_single($entity_type, $entity_id);
+  }
+  else {
+    // Create a node.
+    $node = new stdClass();
+    $node->title = "A new Node " . REQUEST_TIME;
+    $node->type = "WorkfowField";
+    node_object_prepare($node); // Sets some defaults. Invokes hook_prepare() and hook_node_prepare().
+    $node->language = LANGUAGE_NONE; // Or for example 'en' if locale is enabled.
+    $node->uid = $user->uid;
+    $node->status = 1; //(1 or 0): published or not
+    $node->promote = 0; //(1 or 0): promoted to front page
+    $node->comment = 1; // 0 = comments disabled, 1 = read only, 2 = read/write
+//  // Term reference (taxonomy) field
+  $node->field_workflow[$node->language][]['value'] = 1;
+//  // Entity reference field
+//  $node->field_customer_nid[$node->language][] = array(
+//    'target_id' => $form_state['values']['entity id'],
+//    'target_type' => 'node',
+//  );
+    // 'node' is default,
+    // Other possible values are "user" and  "taxonomy_term".
+    $node = node_submit($node); // Prepare node for saving
+    node_save($node);
+  }
+
+  $t_greetings = "
+    Hello [current-user:name]!
+    <br/>node type + id + title = [node:content-type] + [node:nid] + [node:title]
+  ";
+
+  $t_node_all = '
+ <br />	Workflow last transition	 = 	[node:last-transition]	    	Last workflow state transition of content.
+ <br />	Comment comment	 = 	[node:last-transition:comment]	    	Workflow executed transition "comment" property.
+ <br />	Created Medium format	 = 	[node:last-transition:created:medium]	    	A date in "medium" format. (Fri, 05/30/2014 - 10:59)
+ <br />	Created Raw timestamp	 = 	[node:last-transition:created:raw]	    	A date in UNIX timestamp format (1401440380)
+ <br />	Created Seconds-since	 = 	[node:last-transition:created:seconds]	    	A date in "seconds ago" format (604800). Use it for easy scheduling workflow transitions.
+ <br />	Delta    	 = 	[node:last-transition:delta]	    	Workflow executed transition "delta" property.
+ <br />	Entity_type	 = 	[node:last-transition:entity-type]	    	Workflow executed transition "entity_type" property.
+ <br />	Field_name	 = 	[node:last-transition:field-name]	    	Workflow executed transition "field_name" property.
+ <br />	label    	 = 	[node:last-transition:label]	    	Workflow executed transition "label" property.
+ <br />	Language	 = 	[node:last-transition:language]	    	Workflow executed transition "language" property.
+
+ <br />	New state	 = 	[node:last-transition:new-state]	    	The new state.
+ <br />	  Original workflow state	 = 	[node:last-transition:new-state:original]	    	The original workflow state data if the workflow state is being updated or saved.
+ <br />	  State ID	 = 	[node:last-transition:new-state:sid]	    	The State ID.
+ <br />	  State label	 = 	[node:last-transition:new-state:label]	    	The state label.
+ <br />	  Status	 = 	[node:last-transition:new-state:status]	    	Workflow state "status" property.
+ <br />	  Sysid   	 = 	[node:last-transition:new-state:sysid]	    	Workflow state "sysid" property.
+ <br />	  URL    	 = 	[node:last-transition:new-state:url]	    	The URL of the workflow state.
+ <br />	  Weight	 = 	[node:last-transition:new-state:weight]	    	Workflow state "weight" property.
+ <br />	  Wid    	 = 	[node:last-transition:new-state:wid]	    	Workflow state "wid" property.
+
+ <br />	Nid	 = 	[node:last-transition:nid]	    	Workflow executed transition "nid" property.
+ <br />	Old_sid	 = 	[node:last-transition:old-sid]	    	Workflow executed transition "old_sid" property.
+ <br />	Revision_id = 	[node:last-transition:revision-id]	    	Workflow executed transition "revision_id" property.
+ <br />	Sid	 = 	[node:last-transition:sid]	    	Workflow executed transition "sid" property.
+ <br />	Stamp	 = 	[node:last-transition:stamp]	    	Workflow executed transition "stamp" property.
+ <br />	Uid	 = 	[node:last-transition:uid]	    	Workflow executed transition "uid" property.
+
+ <br />	Old state	 = 	[node:last-transition:old-state]	    	The old state.
+ <br />	  Original workflow state	 = 	[node:last-transition:old-state:original]	    	The original workflow state data if the workflow state is being updated or saved.
+ <br />	  State ID	 = 	[node:last-transition:old-state:sid]	    	The State ID.
+ <br />	  State label	 = 	[node:last-transition:old-state:label]	    	The state label.
+ <br />	  Status	 = 	[node:last-transition:old-state:status]	    	Workflow state "status" property.
+ <br />	  Sysid  	 = 	[node:last-transition:old-state:sysid]	    	Workflow state "sysid" property.
+ <br />	  URL   	 = 	[node:last-transition:old-state:url]	    	The URL of the workflow state.
+ <br />	  Weight	 = 	[node:last-transition:old-state:weight]	    	Workflow state "weight" property.
+ <br />	  Wid   	 = 	[node:last-transition:old-state:wid]	    	Workflow state "wid" property.
+
+ <br />	User	 = 	[node:last-transition:user]	    	The user that executed the transition.
+ <br />	  Created	 = 	[node:last-transition:user:created]	    	The date the user account was created.
+ <br />	  Default theme	 = 	[node:last-transition:user:theme]	    	The user"s default theme.
+ <br />	  Edit URL	 = 	[node:last-transition:user:edit-url]	    	The URL of the account edit page.
+ <br />	  Email	 = 	[node:last-transition:user:mail]	    	The email address of the user account.
+ <br />	  Last access	 = 	[node:last-transition:user:last-access]	    	The date the user last accessed the site.
+ <br />	  Last login	 = 	[node:last-transition:user:last-login]	    	The date the user last logged in to the site.
+ <br />	  Name	 = 	[node:last-transition:user:name]	    	The login name of the user account.
+ <br />	  Original user	 = 	[node:last-transition:user:original]	    	The original user data if the user is being updated or saved.
+ <br />	  Picture	 = 	[node:last-transition:user:picture]	    	The picture of the user.
+ <br />	  Roles	 = 	[node:last-transition:user:roles]	    	The user roles associated with the user account.
+ <br />	  Status	 = 	[node:last-transition:user:status]	    	Whether the user is active or blocked.
+ <br />	  URL	 = 	[node:last-transition:user:url]	    	The URL of the account profile page.
+ <br />	  User ID	 = 	[node:last-transition:user:uid]	    	The unique ID of the user account.
+
+ <br />	Workflow	 = 	[node:last-transition:Workflow]	    	Workflow the state belongs to.
+ <br />	  Workflow ID	 = 	[node:last-transition:Workflow:wid]	    	The ID used to identify this workflow internally.
+ <br />	  Module	 = 	[node:last-transition:Workflow:module]	    	Workflow "module" property.
+ <br />	  Options	 = 	[node:last-transition:Workflow:options]	    	Workflow "options" property.
+ <br />	  Original workflow	 = 	[node:last-transition:Workflow:original]	    	The original workflow data if the workflow is being updated or saved.
+ <br />	  States of this Workflow	 = 	[node:last-transition:Workflow:states]	    	States of this Workflow
+ <br />	  Status	 = 	[node:last-transition:Workflow:status]	    	Workflow "status" property.
+ <br />	  Tab_roles	 = 	[node:last-transition:Workflow:tab-roles]	    	Workflow "tab_roles" property.
+ <br />	  Transitions of this Workflow	 = 	[node:last-transition:Workflow:transitions]	    	Transitions of this Workflow
+ <br />	  URL	 = 	[node:last-transition:Workflow:url]	    	The URL of the workflow.
+ <br />	  Workflow ID	 = 	[node:last-transition:Workflow:wid]	    	The unique ID of the workflow applied to this node.
+ <br />	  Workflow label	 = 	[node:last-transition:Workflow:label]	    	The workflow applied to this node.
+
+ <br />	  Workflow executed transition ID	 = 	[node:last-transition:hid]	    	The unique ID of the workflow executed transition.
+
+  ';
+
+// This text is to test only a few tokens, for better inspection.
+  $t_node_sub = '
+ <br />	User	 = 	[node:last-transition:user]	    	The user that executed the transition.
+ <br />	label    	 = 	[node:last-transition:label]	    	Workflow executed transition "label" property.
+  ';
+
+// todo : test old-state
+  $data = array();
+  $data['node'] = $node;
+  $data['entity'] = $node;
+  $data['entity_type'] = 'node';
+
+  // Display the tokenized text.
+  dpm($node);
+  dpm(token_replace(  $t_greetings, $data));
+  dpm(token_replace(  $t_node_all, $data));
+//  dpm(token_replace(  $t_node_sub, $data));
+
+}
+
+function _workflow_test_tokens_term($entity_id = '2', $entity_type = 'taxonomy_term', $field_name = NULL) {
+  $token_type = 'term';
+
+  global $user;
+
+  if ($entity_id) {
+    $node = entity_load_single($entity_type, $entity_id);
+  }
+  else {
+    // Create a term.
+  }
+
+  $t_greetings = "
+    Hello [current-user:name]!
+    <br/>node type + id + title = [term:content-type] + [term:tid] + [term:title]
+  ";
+
+  $t_node_all = '
+ <br />	Workflow last transition	 = 	[term:last-transition]	    	Last workflow state transition of content.
+ <br />	Comment comment	 = 	[term:last-transition:comment]	    	Workflow executed transition "comment" property.
+ <br />	Created	                 = 	[term:last-transition:created]	    	The date the transition was created.
+ <br />	Created Medium format	 = 	[term:last-transition:created:medium]	    	A date in "medium" format. (Fri, 05/30/2014 - 10:59)
+ <br />	Created Raw timestamp	 = 	[term:last-transition:created:raw]	    	A date in UNIX timestamp format (1401440380)
+ <br />	Created Seconds-since	 = 	[term:last-transition:created:seconds]	    	A date in "seconds ago" format (604800). Use it for easy scheduling workflow transitions.
+ <br />	Delta    	 = 	[term:last-transition:delta]	    	Workflow executed transition "delta" property.
+ <br />	Entity_type	 = 	[term:last-transition:entity-type]	    	Workflow executed transition "entity_type" property.
+ <br />	Field_name	 = 	[term:last-transition:field-name]	    	Workflow executed transition "field_name" property.
+ <br />	label    	 = 	[term:last-transition:label]	    	Workflow executed transition "label" property.
+ <br />	Language	 = 	[term:last-transition:language]	    	Workflow executed transition "language" property.
+
+ <br />	New state	 = 	[term:last-transition:new-state]	    	The new state.
+ <br />	  Original workflow state	 = 	[term:last-transition:new-state:original]	    	The original workflow state data if the workflow state is being updated or saved.
+ <br />	  State ID	 = 	[term:last-transition:new-state:sid]	    	The State ID.
+ <br />	  State label	 = 	[term:last-transition:new-state:label]	    	The state label.
+ <br />	  Status	 = 	[term:last-transition:new-state:status]	    	Workflow state "status" property.
+ <br />	  Sysid   	 = 	[term:last-transition:new-state:sysid]	    	Workflow state "sysid" property.
+ <br />	  URL    	 = 	[term:last-transition:new-state:url]	    	The URL of the workflow state.
+ <br />	  Weight	 = 	[term:last-transition:new-state:weight]	    	Workflow state "weight" property.
+ <br />	  Wid    	 = 	[term:last-transition:new-state:wid]	    	Workflow state "wid" property.
+
+ <br />	Nid	 = 	[term:last-transition:nid]	    	Workflow executed transition "nid" property.
+ <br />	Old_sid	 = 	[term:last-transition:old-sid]	    	Workflow executed transition "old_sid" property.
+ <br />	Revision_id = 	[term:last-transition:revision-id]	    	Workflow executed transition "revision_id" property.
+ <br />	Sid	 = 	[term:last-transition:sid]	    	Workflow executed transition "sid" property.
+ <br />	Stamp	 = 	[term:last-transition:stamp]	    	Workflow executed transition "stamp" property.
+ <br />	Uid	 = 	[term:last-transition:uid]	    	Workflow executed transition "uid" property.
+
+ <br />	Old state	 = 	[term:last-transition:old-state]	    	The old state.
+ <br />	  Original workflow state	 = 	[term:last-transition:old-state:original]	    	The original workflow state data if the workflow state is being updated or saved.
+ <br />	  State ID	 = 	[term:last-transition:old-state:sid]	    	The State ID.
+ <br />	  State label	 = 	[term:last-transition:old-state:label]	    	The state label.
+ <br />	  Status	 = 	[term:last-transition:old-state:status]	    	Workflow state "status" property.
+ <br />	  Sysid  	 = 	[term:last-transition:old-state:sysid]	    	Workflow state "sysid" property.
+ <br />	  URL   	 = 	[term:last-transition:old-state:url]	    	The URL of the workflow state.
+ <br />	  Weight	 = 	[term:last-transition:old-state:weight]	    	Workflow state "weight" property.
+ <br />	  Wid   	 = 	[term:last-transition:old-state:wid]	    	Workflow state "wid" property.
+
+ <br />	User	 = 	[term:last-transition:user]	    	The user that executed the transition.
+ <br />	  Created	 = 	[term:last-transition:user:created]	    	The date the user account was created.
+ <br />	  Default theme	 = 	[term:last-transition:user:theme]	    	The user"s default theme.
+ <br />	  Edit URL	 = 	[term:last-transition:user:edit-url]	    	The URL of the account edit page.
+ <br />	  Email	 = 	[term:last-transition:user:mail]	    	The email address of the user account.
+ <br />	  Last access	 = 	[term:last-transition:user:last-access]	    	The date the user last accessed the site.
+ <br />	  Last login	 = 	[term:last-transition:user:last-login]	    	The date the user last logged in to the site.
+ <br />	  Name	 = 	[term:last-transition:user:name]	    	The login name of the user account.
+ <br />	  Original user	 = 	[term:last-transition:user:original]	    	The original user data if the user is being updated or saved.
+ <br />	  Picture	 = 	[term:last-transition:user:picture]	    	The picture of the user.
+ <br />	  Roles	 = 	[term:last-transition:user:roles]	    	The user roles associated with the user account.
+ <br />	  Status	 = 	[term:last-transition:user:status]	    	Whether the user is active or blocked.
+ <br />	  URL	 = 	[term:last-transition:user:url]	    	The URL of the account profile page.
+ <br />	  User ID	 = 	[term:last-transition:user:uid]	    	The unique ID of the user account.
+
+ <br />	Workflow	 = 	[term:last-transition:Workflow]	    	Workflow the state belongs to.
+ <br />	  Workflow ID	 = 	[term:last-transition:Workflow:wid]	    	The ID used to identify this workflow internally.
+ <br />	  Module	 = 	[term:last-transition:Workflow:module]	    	Workflow "module" property.
+ <br />	  Options	 = 	[term:last-transition:Workflow:options]	    	Workflow "options" property.
+ <br />	  Original workflow	 = 	[term:last-transition:Workflow:original]	    	The original workflow data if the workflow is being updated or saved.
+ <br />	  States of this Workflow	 = 	[term:last-transition:Workflow:states]	    	States of this Workflow
+ <br />	  Status	 = 	[term:last-transition:Workflow:status]	    	Workflow "status" property.
+ <br />	  Tab_roles	 = 	[term:last-transition:Workflow:tab-roles]	    	Workflow "tab_roles" property.
+ <br />	  Transitions of this Workflow	 = 	[term:last-transition:Workflow:transitions]	    	Transitions of this Workflow
+ <br />	  URL	 = 	[term:last-transition:Workflow:url]	    	The URL of the workflow.
+ <br />	  Workflow ID	 = 	[term:last-transition:Workflow:wid]	    	The unique ID of the workflow applied to this node.
+ <br />	  Workflow label	 = 	[term:last-transition:Workflow:label]	    	The workflow applied to this node.
+
+ <br />	  Workflow executed transition ID	 = 	[term:last-transition:hid]	    	The unique ID of the workflow executed transition.
+  ';
+
+// This text is to test only a few tokens, for better inspection.
+  $t_node_sub = '
+ <br />	Workflow	 = 	[term:last-transition:Workflow]	    	Workflow the state belongs to.
+  ';
+
+  $data = array();
+  // $data['node'] = $node;
+  $data['term'] = $node;
+  $data['entity'] = $node;
+  $data['entity_type'] = $entity_type;
+  $data['token_type'] = $token_type;
+  $options['sanitize'] = FALSE;
+  $options['clear'] = TRUE;
+  $options['language'] = NULL;
+//dpm($node);
+//dpm($data);
+  // Display the tokenized text.
+//  dpm(token_replace($t_greetings, $data));
+  dpm(token_replace($t_node_all, $data, $options));
+}
+
+/**
+ * Test, taken from CommentTokenReplaceTestCase::testCommentTokenReplacement
+ */
+/*
+function WorkflowCommentTokenReplacement() {
+  global $language;
+  $url_options = array(
+    'absolute' => TRUE,
+    'language' => $language,
+  );
+
+  $this->drupalLogin($this->admin_user);
+
+  // Set comment variables.
+  $this->setCommentSubject(TRUE);
+
+  // Create a node and a comment.
+  $node = $this->drupalCreateNode(array('type' => 'article'));
+  $parent_comment = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE);
+
+  // Post a reply to the comment.
+  $this->drupalGet('comment/reply/' . $node->nid . '/' . $parent_comment->id);
+  $child_comment = $this->postComment(NULL, $this->randomName(), $this->randomName());
+  $comment = comment_load($child_comment->id);
+  $comment->homepage = 'http://example.org/';
+
+  // Add HTML to ensure that sanitation of some fields tested directly.
+  $comment->subject = '<blink>Blinking Comment</blink>';
+  $instance = field_info_instance('comment', 'body', 'comment_body');
+
+  // Generate and test sanitized tokens.
+  $tests = array();
+  $tests['[comment:cid]'] = $comment->cid;
+  $tests['[comment:hostname]'] = check_plain($comment->hostname);
+  $tests['[comment:name]'] = filter_xss($comment->name);
+  $tests['[comment:mail]'] = check_plain($this->admin_user->mail);
+  $tests['[comment:homepage]'] = check_url($comment->homepage);
+  $tests['[comment:title]'] = filter_xss($comment->subject);
+  $tests['[comment:body]'] = _text_sanitize($instance, LANGUAGE_NONE, $comment->comment_body[LANGUAGE_NONE][0], 'value');
+  $tests['[comment:url]'] = url('comment/' . $comment->cid, $url_options + array('fragment' => 'comment-' . $comment->cid));
+  $tests['[comment:edit-url]'] = url('comment/' . $comment->cid . '/edit', $url_options);
+  $tests['[comment:created:since]'] = format_interval(REQUEST_TIME - $comment->created, 2, $language->language);
+  $tests['[comment:changed:since]'] = format_interval(REQUEST_TIME - $comment->changed, 2, $language->language);
+  $tests['[comment:parent:cid]'] = $comment->pid;
+  $tests['[comment:parent:title]'] = check_plain($parent_comment->subject);
+  $tests['[comment:node:nid]'] = $comment->nid;
+  $tests['[comment:node:title]'] = check_plain($node->title);
+  $tests['[comment:author:uid]'] = $comment->uid;
+  $tests['[comment:author:name]'] = check_plain($this->admin_user->name);
+
+  // Test to make sure that we generated something for each token.
+  $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
+
+  foreach ($tests as $input => $expected) {
+    $output = token_replace($input, array('comment' => $comment), array('language' => $language));
+    $this->assertEqual($output, $expected, format_string('Sanitized comment token %token replaced.', array('%token' => $input)));
+  }
+
+  // Generate and test unsanitized tokens.
+  $tests['[comment:hostname]'] = $comment->hostname;
+  $tests['[comment:name]'] = $comment->name;
+  $tests['[comment:mail]'] = $this->admin_user->mail;
+  $tests['[comment:homepage]'] = $comment->homepage;
+  $tests['[comment:title]'] = $comment->subject;
+  $tests['[comment:body]'] = $comment->comment_body[LANGUAGE_NONE][0]['value'];
+  $tests['[comment:parent:title]'] = $parent_comment->subject;
+  $tests['[comment:node:title]'] = $node->title;
+  $tests['[comment:author:name]'] = $this->admin_user->name;
+
+  foreach ($tests as $input => $expected) {
+    $output = token_replace($input, array('comment' => $comment), array('language' => $language, 'sanitize' => FALSE));
+    $this->assertEqual($output, $expected, format_string('Unsanitized comment token %token replaced.', array('%token' => $input)));
+  }
+
+  // Load node so comment_count gets computed.
+  $node = node_load($node->nid);
+
+  // Generate comment tokens for the node (it has 2 comments, both new).
+  $tests = array();
+  $tests['[node:comment-count]'] = 2;
+  $tests['[node:comment-count-new]'] = 2;
+
+  foreach ($tests as $input => $expected) {
+    $output = token_replace($input, array('node' => $node), array('language' => $language));
+    $this->assertEqual($output, $expected, format_string('Node comment token %token replaced.', array('%token' => $input)));
+  }
+}
+ */

+ 386 - 199
sites/all/modules/contrib/admin/workflow/workflow.tokens.inc

@@ -1,229 +1,416 @@
 <?php
+
 /**
  * @file
  * Tokens hooks for Workflow module.
+ *
+ * To every entity type, a default Workflow token tree is added. To support
+ * multiple tokens per entity bundle, an extra token tree 'Workflows' is
+ * created.
+ *
+ * How to test?
+ * - Enable module 'Token'; use page admin/help/token;
+ * - Enable module 'Token example'; use page examples/token;
+ * - Enable module Automatic Entity Label, set a label, and save entity.
+ */
+
+/**
+ * Implements drupal_alter('token_field_info', $info) from token.tokens.inc.
+ *
+ * It would be nice if this would work! But it doesn't...
+ */
+/*
+// function workflow_token_field_info_alter(&$info) {
+//   $workflow_field_info = _workflow_info_fields();
+//   foreach($workflow_field_info as $field_name => $field_info) {
+//     if (array_key_exists($field_name, $info)) {
+//       $info[$field_name]['type'] = 'WorkflowLastTransition';
+//       $info[$field_name]['module'] = 'workflow';
+//     }
+//   }
+// }
+ */
+
+/**
+ * Adds a subtree to each WorkflowField.
+ *
+ * ATM we only generate tokens for the last transition of a field.
  */
+function workflow_token_info_alter(&$data) {
+
+  foreach ($data['tokens'] as $object => &$tokens) {
+    // Add a token for scheduling, in 'seconds ago' format.
+    if ($object == 'date' && !isset($tokens['seconds'])) {
+      $tokens['seconds'] = array(
+        'name' => 'Seconds-since',
+        'description' => "A date in 'seconds ago' format (<i>604800</i>). Use it for easy scheduling workflow transitions.",
+        'module' => 'workflow',
+      );
+    }
+
+    // High-jack the fields (they do not have sub-tokens, yet).
+    foreach ($tokens as &$token) {
+      // Caveat: the following algorithm is just a guess.
+      if (isset($token['module']) && $token['module'] == 'token') {
+        if (isset($token['description']) && 0 == substr_compare($token['description'], 'Workflow', 0, 8)) {
+          $token['type'] = 'WorkflowTransition';
+          $token['module'] = 'workflow';
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_token_info().
+ *
+ * Adds tokens and token types, for Field, Workflow, State and Transition,
+ * using the names of the entities from Workflow module.
+ * Lots of tokens are already defined via entity_properties.
+ * D7: a dependency on entity_token exists.
+ *
+ * @see workflow_entity_property_info_alter()
+ */
+function workflow_token_info() {
+
+  if (!module_exists('entity_token')) {
+    return array();
+  }
+
+  /*
+   * Token types.
+   */
+  $types['WorkflowField'] = array(
+    'name' => t('Workflows'),
+    'description' => t('Tokens related to workflows.'),
+    'needs-data' => 'entity',
+  );
+  $types['WorkflowTransition'] = array(
+    'name' => t('Workflow Transition'),
+    'description' => t('Tokens related to workflow transitions.'),
+    // 'needs-data' => 'entity',
+    'needs-data' => 'WorkflowField',
+  );
+  $types['WorkflowState'] = array(
+    'name' => t('Workflow State'),
+    'description' => t('Tokens related to workflow state.'),
+    'needs-data' => 'WorkflowTransition',
+  );
+  $types['Workflow'] = array(
+    'name' => t('Workflow'),
+    'description' => t('Tokens related to workflows.'),
+    'needs-data' => 'WorkflowTransition',
+  );
+
+  /*
+   * Chained tokens for nodes.
+   */
+  $last_transition = array(
+    'name' => t('Workflow last transition'),
+    'description' => t('Last workflow state transition of content.'),
+    'type' => 'WorkflowTransition',
+    'module' => 'workflow',
+  );
+
+  // Add a token tree to each core entity type. This allows easy reference
+  // in the majority of cases.
+  $workflow_field['last-transition'] = $last_transition;
+  $entity['last-transition'] = $last_transition;
+  $user['last-transition'] = $last_transition;
+  $node['last-transition'] = $last_transition;
+  $term['last-transition'] = $last_transition;
+
+  $return = array(
+    'types' => $types,
+    'tokens' => array(
+      // 'entity' => $entity, // #2272121
+      'user' => $user,
+      'node' => $node,
+      'term' => $term,
+      'WorkflowField' => $workflow_field,
+      // 'WorkflowTransition' => $workflow_transition,
+      // 'WorkflowState' => $workflow_state,
+      // 'Workflow' => $workflow,
+    ),
+  );
+
+  return $return;
+}
 
 /**
  * Implements hook_tokens().
+ *
+ * N.B. Keep the following functions aligned when changing properties:
+ * - workflow_tokens()
+ * - workflow_entity_property_info_alter()
+ * - workflow_views_views_data_alter()
  */
 function workflow_tokens($type, $tokens, array $data = array(), array $options = array()) {
   $replacements = array();
+
   $sanitize = !empty($options['sanitize']);
-  $langcode = !empty($options['language']->language) ? $options['language']->language : LANGUAGE_NONE;
-  $date = REQUEST_TIME;
-
-  if (($type == 'node' || $type == 'workflow') && !empty($data['node'])  && !empty($data['node']->nid)) {
-    $node = $data['node'];
-
-    // Get a list of all possible states for returning state names.
-    $states = workflow_get_workflow_states_all();
-
-    if ($workflow = workflow_get_workflow_type_map_by_type($node->type)) {
-      // @TODO: If we ever move Workflow to allow M:M content types to workflows, this will need updating.
-      if ($workflow = workflow_get_workflows_by_wid($workflow->wid)) {
-        $wid = $workflow->wid;
-        $last_history = workflow_get_recent_node_history($node->nid);
-
-        if (isset($node->workflow) && !isset($node->workflow_stamp)) {
-          // The node is being submitted but the form data has not been saved to the database yet,
-          // so we set the token values from the workflow form fields.
-          $sid = $node->workflow;
-          $old_sid = isset($last_history->old_sid) ? $last_history->old_sid : workflow_get_creation_state_by_wid($workflow->wid);
-          $user_name = $node->uid ? (isset($node->name) ? $node->name
-            : variable_get('anonymous', 'Anonymous'))
-            : variable_get('anonymous', 'Anonymous');
-          $uid = $node->uid;
-          $account = user_load($node->uid);
-          $mail = ($node->uid && isset($node->user_mail)) ? $node->user_mail : '';
-          $comment = isset($node->workflow_comment) ? $node->workflow_comment : '';
-        }
-        else {
-          if (empty($last_history)) {
-            // If the node has no workflow history,
-            // the node is being inserted and will soon be transitioned to the first valid state.
-            // We find this state using the same logic as workflow_nodeapi().
-            if ($choices = workflow_field_choices($node)) {
-              // @TODO: Some users, like anonymous, may not be allowed
-              // to cause transitions, so there will be no choices.
-              $keys = array_keys($choices);
-              $sid = array_shift($keys);
-              $old_sid = workflow_get_creation_state_by_wid($workflow->wid);
-            }
-            else {
-              // What to choose?
-              $sid = $node->workflow;
-              $old_sid = $workflow->creation_state;
+  $langcode = isset($options['language']) ? $options['language']->language : NULL;
+
+  // The 'node' tokens have already been replaced with 'entity'.
+  // Skip for easier debugging.
+  // @todo: is this always the case, or only if Entity Tokens is enabled?
+  if ($type == 'node' || $type == 'user' || $type == 'term') {
+    return $replacements;
+  }
+
+  if ($type == 'date' && !empty($data['date'])) {
+    foreach ($tokens as $name => $original) {
+      switch ($name) {
+        case 'seconds':
+          // This is our custom date token in 'seconds ago' format.
+          $seconds = REQUEST_TIME - $data['date'];
+          $replacements[$original] = $seconds;
+          // Avoid reporcessing in the remainder of this function.
+          break;
+      }
+    }
+    return $replacements;
+  }
+  elseif ($type == 'entity' && !empty($data['entity'])) {
+    // new, chained tokens, as of version 7.x-2.3.
+    $entity = $data['entity'];
+    $entity_type = $data['entity_type'];
+    // $token_type = $data['token_type'];
+
+    foreach ($tokens as $name => $original) {
+      if (FALSE !== strpos($name, ':')) {
+        // This is a chained property (contains ':').
+        $name_parts = explode(':', $name);
+        switch (end($name_parts)) {
+          case 'comment':
+            if (isset($entity->workflow_transitions) && isset($entity->workflow_transitions[$name_parts[0]])) {
+              $replacements[$original] = $entity->workflow_transitions[$name_parts[0]]->$name_parts[1];
             }
-            $sid = workflow_get_workflow_states_by_sid($sid)->sid;
-            $old_sid = workflow_get_workflow_states_by_sid($old_sid)->sid;
-            $user_name = $node->uid ? $node->name : variable_get('anonymous', 'Anonymous');
-            $uid = $node->uid;
-            $account = user_load($node->uid);
-            $mail = ($node->uid && isset($node->user_mail)) ? $node->user_mail : '';
-            $comment = isset($node->workflow_comment) ? $node->workflow_comment : '';
+            break;
+        }
+      }
+      elseif (isset($data['workflow_token_type'])) {
+        // This part taken from entity_token_tokens().
+        $wrapper = entity_metadata_wrapper($data['workflow_token_type'], $data[$data['workflow_token_type']]);
+        $property_name = str_replace('-', '_', $name);
+        try {
+          if ($name == 'workflow' ||
+            $name == 'states' ||
+            $name == 'transitions'
+          ) {
+            $replacement = _workflow_token_get_token($wrapper->$property_name, $options);
           }
           else {
-            // Sometimes there is no workflow set (edit?).
-            if (!isset($node->workflow)) {
-              $node->workflow = $last_history->sid;
-            }
-            // Is a transition in progress?
-            if ($node->workflow != $last_history->sid) {
-              $sid = $node->workflow;
-              $old_sid = $last_history->sid;
-              $date = $node->workflow_stamp;
-              $uid = $node->uid;
-              $account = user_load($node->uid);
-            }
-            else {
-              // Not a transition.
-              $sid = $last_history->sid;
-              $old_sid = $last_history->old_sid;
-              $date = $last_history->stamp;
-              $uid = $last_history->uid;
-              $account = user_load($last_history->uid);
-            }
-
-            // Default to the most recent transition data in the workflow history table.
-            $user_name = $account->uid ? $account->name : variable_get('anonymous', 'Anonymous');
-            $mail = $account->uid ? $account->mail : '';
-            $comment = $last_history->comment;
+            $replacement = _entity_token_get_token($wrapper->$property_name, $options);
+          }
+          if (isset($replacement)) {
+            $replacements[$original] = $replacement;
           }
+        } catch (EntityMetadataWrapperException $e) {
+          // dpm('token not found: ' . $name);
+          // If tokens for not existing values are requested, just do nothing.
         }
+      }
+    }
 
-        foreach ($tokens as $name => $original) {
-          switch ($name) {
-            case 'workflow-name':
-              $replacements[$original] = $sanitize ? check_plain($workflow->name) : $workflow->name;
-              break;
-            case 'workflow-current-state-name':
-              $replacements[$original] = $sanitize ? check_plain($states[$sid]) : $states[$sid];
-              break;
-            case 'workflow-old-state-name':
-              $replacements[$original] = $sanitize ? check_plain($states[$old_sid]) : $states[$old_sid];
-              break;
-            case 'workflow-current-state-updating-user-name':
-              $name = format_username($account);
-              $replacements[$original] = $sanitize ? check_plain($name) : $name;
-              break;
-            case 'workflow-current-state-updating-user-link':
-              $replacements[$original] = theme('username', array('account' => $account));
-              break;
-            case 'workflow-current-state-updating-user-uid':
-              // User IDs are integers only and do not need sanitization.
-              $replacements[$original] = $uid;
-              break;
-            case 'workflow-current-state-updating-user-mail':
-              $replacements[$original] = $sanitize ? check_plain($account->mail) : $account->mail;
-              break;
-            case 'workflow-current-state-updating-user-mailto':
-              $replacements[$original] = '<a href="mailto:' . check_plain($account->mail) . '">' . check_plain($user_name) . '</a>';
-              break;
-            case 'workflow-current-state-log-entry':
-              $replacements[$original] = $sanitize ? check_markup($comment, filter_default_format(), $langcode) : $comment;
-              break;
-            case 'workflow-current-state-date-iso':
-              $replacements[$original] = format_date($date, 'custom', 'c', NULL, $langcode);
-              break;
-            case 'workflow-current-state-date-tstamp':
-              $replacements[$original] = $sanitize ? check_plain($date) : $date;
-              break;
-            case 'workflow-current-state-date-formatted':
-              $replacements[$original] = format_date($date, 'medium', '', NULL, $langcode);
-              break;
-            default:
-              // Add support for custom date formats. (see token.tokens.inc)
-              // @todo Remove when http://drupal.org/node/1173706 is fixed.
-              $date_format_types = system_get_date_types();
-              foreach ($date_format_types as $date_type => $date_info) {
-                if ($name == 'workflow-current-state-date-' . $date_type) {
-                  $replacements[$original] = format_date($date, $date_type, '', NULL, $langcode);
-                }
-              }
-              break;
-          }
+    // If this is a Last Transition, get subtokens for it.
+    if ($sub_tokens = token_find_with_prefix($tokens, 'last-transition')) {
+      // Get the workflow tokens from the transition of this entity.
+      $transition = _workflow_tokens_get_transition($entity_type, $entity, NULL);
+      $sub_data = array(
+        'WorkflowTransition' => $transition,
+        'workflow_token_type' => 'WorkflowTransition',
+      );
+      $sub_data += $data;
+      $replacements += token_generate('entity', $sub_tokens, $sub_data, $options);
+    }
+
+    if (isset($data['WorkflowTransition'])) {
+      // If this is a WorkflowField, get subtokens for it.
+      $name_parts = explode(':', $name, 2);
+      $field_name = reset($name_parts);
+      if ($field_name != 'created'
+          && isset($entity->{$field_name})
+          && $sub_tokens = token_find_with_prefix($tokens, $field_name)
+      ) {
+        $transition = _workflow_tokens_get_transition($entity_type, $entity, $field_name);
+        $sub_data = array(
+          'WorkflowTransition' => $transition,
+          'workflow_token_type' => 'WorkflowTransition',
+        );
+        $sub_data += $data;
+        $replacements += token_generate('entity', $sub_tokens, $sub_data, $options);
+      }
+
+      $transition = $data['WorkflowTransition'];
+      $field_name = $transition->field_name;
+
+      if ($sub_tokens = token_find_with_prefix($tokens, 'Workflow')) {
+        $sub_data = array(
+          'Workflow' => $transition->getWorkflow(),
+          'workflow_token_type' => 'Workflow',
+        );
+        $sub_data += $data;
+        $replacements += token_generate('entity', $sub_tokens, $sub_data, $options);
+      }
+
+      // Unify to old-state, new-state.
+      // Do not use underscores, or workflow_tokens will not work!
+      if ($sub_tokens = token_find_with_prefix($tokens, 'old-state')) {
+        $sub_data = array(
+          'WorkflowState' => $transition->getOldState(),
+          'workflow_token_type' => 'WorkflowState',
+        );
+        $sub_data += $data;
+        $replacements += token_generate('entity', $sub_tokens, $sub_data, $options);
+      }
+      if ($sub_tokens = token_find_with_prefix($tokens, 'new-state')) {
+        $sub_data = array(
+          'WorkflowState' => $transition->getNewState(),
+          'workflow_token_type' => 'WorkflowState',
+        );
+        $sub_data += $data;
+        $replacements += token_generate('entity', $sub_tokens, $sub_data, $options);
+      }
+      if ($sub_tokens = token_find_with_prefix($tokens, 'user')) {
+        if (isset($data['WorkflowTransition'])) {
+          $sub_entity = $data['WorkflowTransition'];
         }
+        $sub_data = array(
+          'entity_type' => 'user',
+          'user' => user_load($sub_entity->uid),
+          'token_type' => 'user',
+        );
+        $replacements += token_generate('user', $sub_tokens, $sub_data, $options);
+      }
+      if ($sub_tokens = token_find_with_prefix($tokens, 'created')) {
+        $sub_data = array(
+          'entity_type' => 'date',
+          'date' => $data['WorkflowTransition']->stamp,
+          'token_type' => 'date',
+        );
+        $replacements += token_generate('date', $sub_tokens, $sub_data, $options);
       }
     }
   }
-
   return $replacements;
 }
 
 /**
- * Implements hook_token_info().
+ * Gets the token replacement by correctly obeying the options.
+ *
+ * Taken from _entity_token_get_token().
  */
-function workflow_token_info() {
-  $type = array(
-    'name' => t('Workflow'),
-    'description' => t('Tokens related to workflows.'),
-    'needs-data' => 'node',
-    );
-
-  $tokens = array();
-  $tokens['workflow-name'] = array(
-    'name' => t('Workflow name'),
-    'description' => t('Name of workflow appied to this node'),
-    );
-  $tokens['workflow-current-state-name'] = array(
-    'name' => t('Current state name'),
-    'description' => t('Current state of content'),
-    );
-  $tokens['workflow-old-state-name'] = array(
-    'name' => t('Old state name'),
-    'description' => t('Old state of content'),
-    );
-  $tokens['workflow-current-state-updating-user-name'] = array(
-    'name' => t('Username of last state changer'),
-    'description' => t('Username of last state changer'),
-    );
-  $tokens['workflow-current-state-updating-user-link'] = array(
-    'name' => t('Username link of last state changer'),
-    'description' => t('Themed Username of last state changer'),
-    );
-  $tokens['workflow-current-state-updating-user-uid'] = array(
-    'name' => t('Uid of last state changer'),
-    'description' => t('uid of last state changer'),
-    );
-  $tokens['workflow-current-state-updating-user-mail'] = array(
-    'name' => t('Email of last state changer'),
-    'description' => t('email of last state changer'),
-    );
-  $tokens['workflow-current-state-updating-user-mail'] = array(
-    'name' => t('Email link of last state changer'),
-    'description' => t('email link of last state changer'),
-    );
-  $tokens['workflow-current-state-log-entry'] =array(
-    'name' => t('Last workflow comment log'),
-    'description' => t('Last workflow comment log'),
-    );
-  $tokens['workflow-current-state-date-iso'] = array(
-    'name' => t('Current state date (ISO)'),
-    'description' => t('Date of last state change (ISO)'),
-    );
-  $tokens['workflow-current-state-date-tstamp'] = array(
-    'name' => t('Current state date (timestamp)'),
-    'description' => t('Date of last state change (timestamp)'),
-    );
-  $tokens['workflow-current-state-date-formatted'] = array(
-    'name' => t('Current state date (formatted by site default)'),
-    'description' => t('Date of last state change (formatted by site default)'),
-    );
-
-  // Add support for custom date formats. (see token.tokens.inc)
-  // @todo Remove when http://drupal.org/node/1173706 is fixed.
-  $date_format_types = system_get_date_types();
-  foreach ($date_format_types as $date_type => $date_info) {
-    $tokens['workflow-current-state-date-' . $date_type] = array(
-      'name' => t('Current state date (using @format format)', array('@format' => $date_info['title'])),
-      'description' => t("A date in '@type' format. (%date)", array('@type' => $date_type, '%date' => format_date(REQUEST_TIME, $date_type))),
-      'module' => 'workflow',
-      );
+function _workflow_token_get_token($wrapper, $options) {
+
+  // if ($wrapper->value() === NULL) {
+  // // Do not provide a replacement if there is no value.
+  // return NULL;
+  // }
+
+  if (empty($options['sanitize'])) {
+    // When we don't need sanitized tokens decode already sanitizied texts.
+    $options['decode'] = TRUE;
   }
+  $langcode = isset($options['language']) ? $options['language']->language : LANGUAGE_NONE;
 
-  return array(
-    'types' => array('workflow' => $type),
-    'tokens' => array('workflow' => $tokens, 'node' => $tokens),
-  );
+  // If there is a label for a property, e.g., defined by an options list or an
+  // entity label, make use of it.
+  if ($label = $wrapper->label()) {
+    return empty($options['sanitize']) ? $label : check_plain(t($label, array(), array('langcode' => $langcode)));
+  }
+
+  switch ($wrapper->type()) {
+    case 'Workflow':
+    case 'WorkflowState':
+    case 'WorkflowTransition':
+      return $wrapper->value();
+  }
+
+  // Care for outputing list values.
+  if ($wrapper instanceof EntityListWrapper) {
+    $output = array();
+    foreach ($wrapper as $item) {
+      $output[] = _workflow_token_get_token($item, $options);
+    }
+    return implode(', ', $output);
+  }
+  // Else we do not have a good string to output, e.g., for struct values. Just
+  // output the string representation of the wrapper.
+  return (string) $wrapper;
+}
+
+/**
+ * Helper function to get the Transition.
+ *
+ * If the node is being created or updated (using the NUMERIC id),
+ * do not read from db, since the field widget has not been processed yet.
+ * @todo: solve this with module weight?
+ */
+function _workflow_tokens_get_transition($entity_type, $entity, $field_name) {
+  global $user;
+  $transitions = array();
+
+  list($entity_id, , $entity_bundle) = entity_extract_ids($entity_type, $entity);
+
+  $langcode = _workflow_metadata_workflow_get_properties($entity, array(), 'langcode', $entity_type, $field_name);
+
+  if (!empty($entity_id) && !isset($entity->original)) {
+    $transition = workflow_transition_load_single($entity_type, $entity_id, $field_name);
+    return $transition;
+  }
+
+  // Get transition data from online-data.
+  // Create dummy transitions, just to set $node->workflow_transitions[].
+  foreach (_workflow_info_fields($entity, $entity_type, $entity_bundle) as $found_name => $field) {
+    // If $field_name = NULL, any workflow_field/node is OK.
+    if ($field_name <> NULL && $found_name != $field_name) {
+      continue;
+    }
+
+    $old_sid = FALSE;
+    $new_sid = $found_name ? _workflow_get_sid_by_items($entity->{$found_name}[$langcode]) : $entity->workflow_sid;
+
+    if (!isset($entity_id)) {
+      // Creating an entity.
+      // When creating a node, and only 1 valid sid is available, then the
+      // widget is not shown. This generates a problem, since the new/old
+      // sid isn't yet loaded in the Entity.
+      if ($new_state = workflow_state_load_single($new_sid)) {
+        $workflow = $new_state->getWorkflow();
+        $old_sid = $workflow->getCreationSid();
+      }
+      else {
+        // $field['settings']['wid'] can be numeric or named.
+        $workflow = workflow_load_single($field['settings']['wid']);
+        $old_sid = $workflow->getCreationSid();
+        $new_sid = $workflow->getFirstSid($entity_type, $entity, $field_name, $user, FALSE);
+      }
+    }
+    elseif (isset($entity->original)) {
+      if ($field_name && !isset($entity->original->{$found_name}[$langcode])) {
+        // When updating a node, that did not have a workflow attached before.
+        // (Happens when you add workflows to existing Entity types.)
+        $old_sid = workflow_node_previous_state($entity, $entity_type, $found_name);
+      }
+      elseif ($field_name) {
+        // Updating an entity.
+        $old_sid = _workflow_get_sid_by_items($entity->original->{$found_name}[$langcode]);
+      }
+      else {
+        $old_sid = workflow_node_previous_state($entity, $entity_type, $found_name);
+      }
+    }
+
+    $transition = new WorkflowTransition();
+    $transition->setValues($entity_type, $entity, $field_name, $old_sid, $new_sid, $user->uid, REQUEST_TIME, '');
+
+    // Store the transition, so it can be easily fetched later on.
+    // Store in an array, to prepare for multiple workflow_fields per entity.
+    // This is a.o. used in hook_entity_update to trigger 'transition post'.
+    $transitions[$field_name] = $transition;
+  }
+
+  $transition = ($field_name && isset($transitions[$field_name])) ? $transitions[$field_name] : reset($transitions);
+  return $transition;
 }

+ 62 - 49
sites/all/modules/contrib/admin/workflow/workflow_access/workflow_access.features.inc

@@ -1,6 +1,7 @@
 <?php
 
 /**
+ * @file
  * Workflow_access records are a **faux-exportable** component.
  */
 
@@ -33,37 +34,44 @@ SQL;
  *
  * Provide a list of all workflows as selectable Feature components.
  *
- * We'll make all workflow access records related to the selected workflows
- * available for export.
+ * Generates the options to choose from.
+ * Using $wid as key. Result is used by hook_features_export().
  */
 function workflow_access_features_export_options() {
   $workflows = array();
-  foreach (workflow_get_workflows() as $workflow) {
-    $workflows[$workflow->name] = t('workflow access configuration for: ') . $workflow->name;
+  foreach (workflow_load_multiple() as $workflow) {
+    $name = $workflow->getName();
+    $workflows[$name] = t('workflow access configuration for: @label', array('@label' => $name));
   }
-
   return $workflows;
 }
 
 /**
  * Implements hook_features_export().
- *
- * Inform Features about dependencies.
  */
 function workflow_access_features_export($data, &$export, $module_name = '') {
-  $roles = _workflow_access_get_affected_roles();
+  $pipe = array();
+
   $export['dependencies']['workflow_access'] = 'workflow_access';
   $export['dependencies']['workflow'] = 'workflow';
-  $pipe = array();
-  foreach ($data as $component) {
-    $export['features']['workflow_access'][$component] = $component;
 
-    // Access configuration for a workflow needs that worfklow to exist.
-    $pipe['workflow'][$component] = $component;
+  $roles = _workflow_access_get_affected_roles();
+
+  foreach ($data as $workflow_name) {
+    if ($workflow = workflow_load_by_name($workflow_name)) {
+
+      $export['features']['workflow_access'][$workflow_name] = $workflow_name;
+      $export['features']['workflow'][$workflow_name] = $workflow_name;
 
-    // And it needs the roles to which rights are granted.
-    foreach ($roles[$component] as $rname) {
-      $pipe['user_role'][$rname] = $rname;
+      // Access configuration for a workflow needs that workflow to exist.
+      $pipe['workflow'][$workflow_name] = $workflow_name;
+
+      // And it needs the roles to which rights are granted.
+      if (isset($roles[$workflow_name])) {
+        foreach ($roles[$workflow_name] as $role_name) {
+          $pipe['user_role'][$role_name] = $role_name;
+        }
+      }
     }
   }
 
@@ -83,15 +91,15 @@ function workflow_access_features_export_render($module_name, $data, $export = N
   // For each selected workflow, fetch all related workflow access records
   // we want to put into code.
   foreach ($data as $workflow_name) {
-    $workflow = workflow_get_workflows_by_name($workflow_name);
-    if (!empty($workflow)) {
-      $states = workflow_get_workflow_states_by_wid($workflow->wid);
-      if (!empty($states)) {
+    $workflow = workflow_load_by_name($workflow_name);
+    if ($workflow) {
+      $states = $workflow->getStates($all = TRUE);
+      if ($states) {
         $code[] = "\n  \$workflows['$workflow_name'] = array();";
         foreach ($states as $state) {
           $access_records = workflow_access_get_features_workflow_access_by_sid($state->sid);
           if (!empty($access_records)) {
-            $state_name = $state->state;
+            $state_name = $state->getName();
             $code[] = "  \$workflows['$workflow_name']['$state_name'] = array();";
             foreach ($access_records as $record) {
               $rname = $record->rname;
@@ -117,21 +125,28 @@ function workflow_access_features_export_render($module_name, $data, $export = N
  * into the workflow_access table.
  */
 function workflow_access_features_rebuild($module) {
-  $workflow_access_records = module_invoke($module, 'workflow_access_features_default_settings');
-  // retrieve the workflow ids
+  // @see d.o. https://www.drupal.org/node/2472501
+//  $workflow_access_records = module_invoke($module, 'workflow_access_features_default_settings');
+  if (!$workflow_access_records = features_get_default('workflow_access', $module)) {
+    return;
+  }
+
+  // Retrieve the workflow IDs.
   $wids = array();
   foreach ($workflow_access_records as $workflow_name => $state) {
-    $workflow = workflow_get_workflows_by_name($workflow_name);
+    $workflow = workflow_load_by_name($workflow_name);
     $wids[$workflow_name] = $workflow->wid;
   }
 
   foreach ($wids as $workflow_name => $wid) {
-    $states = workflow_get_workflow_states_by_wid($wid);
-    foreach ($states as $state) {
-      // Remove all workflow access records for states belonging to this
-      // workflow. We don't want lingering entries - we only want the ones we're
-      // about to insert.
-      workflow_access_delete_workflow_access_by_sid($state->sid);
+    $workflow = workflow_load_by_name($workflow_name);
+    if (!empty($workflow) && $states = $workflow->getStates($all = TRUE)) {
+      foreach ($states as $state) {
+        // Remove all workflow access records for states belonging to this
+        // workflow. We don't want lingering entries - we only want the ones we're
+        // about to insert.
+        workflow_access_delete_workflow_access_by_sid($state->sid);
+      }
     }
   }
 
@@ -139,20 +154,18 @@ function workflow_access_features_rebuild($module) {
   // workflow_name[state_name][role_name] => array(grant_name => 0|1, ...)
   foreach ($workflow_access_records as $workflow_name => $states) {
     foreach ($states as $state_name => $records) {
-      $state = workflow_get_workflow_states_by_wid_state($wids[$workflow_name], $state_name);
-      if (is_array($state)) {
-        $state = array_pop($state);
-      }
-      foreach ($records as $rname => $record) {
-        $record['sid'] = (is_array($state)) ? $state[0]->sid : $state->sid;
-        if ($rname == WORKFLOW_FEATURES_AUTHOR_NAME) {
-          $record['rid'] = -1;
-        }
-        else {
-          $role = user_role_load_by_name($rname);
-          $record['rid'] = $role->rid;
+      if ($state = workflow_state_load_by_name($state_name, $wids[$workflow_name])) {
+        foreach ($records as $rname => $record) {
+          $record['sid'] = ($state) ? $state->sid : $state->sid;
+          if ($rname == WORKFLOW_FEATURES_AUTHOR_NAME) {
+            $record['rid'] = WORKFLOW_ROLE_AUTHOR_RID;
+          }
+          else {
+            $role = user_role_load_by_name($rname);
+            $record['rid'] = $role->rid;
+          }
+          workflow_access_insert_workflow_access_by_sid($record);
         }
-        workflow_access_insert_workflow_access_by_sid($record);
       }
     }
   }
@@ -168,19 +181,19 @@ function workflow_access_features_revert($module) {
 /**
  * Get workflow_access object like below by state id.
  *
- * array(
+ * Array(
  *   'rname' => 'authenticated user',
  *   'grant_view' => 1,
  *   'grant_update' => 0,
  *   'grant_delete' => 0,
  * );
  *
- * State id and workflow id are not returned because they are implicit for a
+ * State ID and Workflow ID are not returned because they are implicit for a
  * given sid.
  */
 function workflow_access_get_features_workflow_access_by_sid($sid) {
-  // Get all workflow access rules for a sid, where wa.rid is either a valid role or -1,
-  // stands for the author
+  // Get all workflow access rules for a sid, where wa.rid is either a valid
+  // role or -1, which stands for the author.
   $sql = <<<SQL
 SELECT
   r.name as rname,
@@ -188,9 +201,9 @@ SELECT
 FROM {workflow_access} wa
   LEFT JOIN {role} r ON wa.rid = r.rid
 WHERE
-  wa.sid = :sid AND (wa.rid = r.rid OR wa.rid = -1)
+  wa.sid = :sid AND (wa.rid = r.rid OR wa.rid = :rid)
 SQL;
-  $results = db_query($sql, array(':sid' => $sid));
+  $results = db_query($sql, array(':sid' => $sid, ':rid' => WORKFLOW_ROLE_AUTHOR_RID));
   $records = $results->fetchAll();
   foreach ($records as $record) {
     if (empty($record->rname)) {

+ 5 - 4
sites/all/modules/contrib/admin/workflow/workflow_access/workflow_access.info

@@ -1,11 +1,12 @@
 name = Workflow access
-description = Content access control based on workflows and roles.
+description = Content access control based on workflows and roles. Depends on the node_access system, so only works for entities of type 'node'.
 dependencies[] = workflow
 package = Workflow
 core = 7.x
-; Information added by drupal.org packaging script on 2013-07-04
-version = "7.x-1.2"
+
+; Information added by Drupal.org packaging script on 2017-01-15
+version = "7.x-2.9+10-dev"
 core = "7.x"
 project = "workflow"
-datestamp = "1372980654"
+datestamp = "1484510888"
 

+ 37 - 14
sites/all/modules/contrib/admin/workflow/workflow_access/workflow_access.install

@@ -5,16 +5,6 @@
  * Workflow access installation.
  */
 
-/**
- * Implements hook_install().
- */
-function workflow_access_install() { }
-
-/**
- * Implements hook_uninstall().
- */
-function workflow_access_uninstall() { }
-
 /**
  * Implements hook_schema().
  */
@@ -23,13 +13,13 @@ function workflow_access_schema() {
     'description' => 'Workflow access tables',
     'fields' => array(
       'sid' => array(
-        'type' => 'serial',
+        'type' => 'int',
+        'description' => 'The Workflow state ID.',
         'not null' => TRUE,
       ),
       'rid' => array(
         'type' => 'int',
         'not null' => TRUE,
-        'default' => 0,
       ),
       'grant_view' => array(
         'type' => 'int',
@@ -53,10 +43,43 @@ function workflow_access_schema() {
         'default' => 0,
       ),
     ),
+    'primary key' => array('sid', 'rid'),
     'indexes' => array(
       'rid' => array('rid'),
-      'sid' => array('sid'),
     ),
   );
   return $schema;
-}
+}
+
+/**
+ * Force rebuild of node access.
+ */
+function workflow_access_disable() {
+  node_access_needs_rebuild(TRUE);
+}
+
+/**
+ * Correct field type of field 'sid' in workflow_access table.
+ */
+function workflow_access_update_7001() {
+  db_drop_primary_key('workflow_access');
+  db_add_primary_key('workflow_access', array('sid', 'rid'));
+  db_drop_index('workflow_access', 'sid');
+  db_drop_index('workflow_access', 'rid');
+
+  db_change_field('workflow_access', 'sid', 'sid', array(
+    'type' => 'int',
+    'not null' => TRUE,
+  ));
+
+  db_drop_primary_key('workflow_access');
+  db_add_primary_key('workflow_access', array('sid', 'rid'));
+  db_add_index('workflow_access', 'rid', array('rid'));
+}
+
+/**
+ * Force rebuild of node access.
+ */
+function workflow_access_update_7002() {
+  node_access_needs_rebuild(TRUE);
+}

+ 171 - 258
sites/all/modules/contrib/admin/workflow/workflow_access/workflow_access.module

@@ -1,271 +1,238 @@
 <?php
+
 /**
  * @file
- *   Provides node access permissions based on workflow states.
+ * Provides node access permissions based on workflow states.
  */
 
 /**
  * Implements hook_menu().
+ *
+ * Uses pattern of EntityWorkflowUIController::hook_menu().
  */
 function workflow_access_menu() {
   $items = array();
 
-  $items["admin/config/workflow/access/%workflow"] = array(
+  $path = 'admin/config/workflow/workflow';
+  $id_count = count(explode('/', $path));
+
+  $items[$path . '/access'] = array(
+    'title' => 'Access settings',
+    'file' => 'workflow_access.pages.inc',
+    'access arguments' => array('administer workflow'),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('workflow_access_priority_form'),
+    'type' => MENU_LOCAL_ACTION,
+  );
+  $items[$path . '/manage/%workflow/access'] = array(
     'title' => 'Access',
+    'file' => 'workflow_access.pages.inc',
     'access arguments' => array('administer workflow'),
     'page callback' => 'drupal_get_form',
-    'page arguments' => array('workflow_access_form', 4),
-    'type' => MENU_CALLBACK,
-    );
+    'page arguments' => array(
+      'workflow_access_form',
+      $id_count + 1,
+      $id_count + 2,
+    ),
+    // 'type' => MENU_CALLBACK,
+    // 'type' => MENU_LOCAL_TASK,
+  );
 
   return $items;
 }
 
 /**
- * Implements hook_node_grants().
- *
- * Supply the workflow access grants. We are simply using
- * roles as access lists, so rids translate directly to gids.
+ * Implements hook_help().
  */
-function workflow_access_node_grants($account, $op) {
-  return array(
-    'workflow_access' => array_keys($account->roles),
-    'workflow_access_owner' => array($account->uid),
-  );
-}
-
-/**
- * Implements hook_node_access_records().
- *
- * Returns a list of grant records for the passed in node object.
- */
-function workflow_access_node_access_records($node) {
-  $grants = array();
-  if ($state = workflow_get_workflow_node_by_nid($node->nid)) {
-    $state = workflow_get_workflow_states_by_sid($state->sid);
-    foreach (workflow_access_get_workflow_access_by_sid($state->sid) as $grant) {
-      $grants[] = array(
-        'realm' => ($grant->rid == -1) ? 'workflow_access_owner' : 'workflow_access',
-        'gid' => ($grant->rid == -1) ? $node->uid : $grant->rid,
-        'grant_view' => $grant->grant_view,
-        'grant_update' => $grant->grant_update,
-        'grant_delete' => $grant->grant_delete,
-        'priority' => variable_get('workflow_access_priority', 0),
+function workflow_access_help($path, $arg) {
+  $help = '';
+  switch ($path) {
+    case 'admin/config/workflow/workflow/manage/%/access':
+      $help = t("WARNING: Use of the 'Edit any', 'Edit own', and even 'View
+        published content' permissions for the content type may override these
+        access settings. You may need to <a href='!url'>alter the priority of
+        the Workflow access module</a>.", array('!url' => url('admin/config/workflow/workflow/access'))
       );
-    }
+
+      if (module_exists('og')) {
+        $help .= '<br>';
+        $help .= t('WARNING: Organic Groups (OG) is present and may interfere
+         with these settings.');
+        if (variable_get('og_node_access_strict', TRUE)) {
+          $help .= ' ';
+          $help .= t('In particular, <a href="!url">Strict node access
+            permissions</a> is enabled and may override Workflow access
+            settings.', array('!url' => url('admin/config/group/settings')));
+        }
+      }
+      break;
+    default:
+      break;
   }
-  return $grants;
+  return $help;
 }
 
 /**
- * Implements hook_node_access_explain().
- * This is a Devel Node Access hook.
+ * Implements hook_features_api().
+ *
+ * Tell the Features module that we intend to provide one exportable component.
  */
-function workflow_access_node_access_explain($row) {
-  static $interpretations = array();
-  switch ($row->realm) {
-    case 'workflow_access_owner':
-      $interpretations[$row->gid] = t('Workflow access: author of the content may access');
-      break;
-    case 'workflow_access':
-      $roles = user_roles();
-      $interpretations[$row->gid] = t('Workflow access: %role may access', array('%role' => $roles[$row->gid]));
-      break;
-  }
-  return (!empty($interpretations[$row->gid]) ? $interpretations[$row->gid] : NULL);
+function workflow_access_features_api() {
+  return array(
+    'workflow_access' => array(
+      'name' => t('Workflow access'),
+      'file' => drupal_get_path('module', 'workflow_access') . '/workflow_access.features.inc',
+      'default_hook' => 'workflow_access_features_default_settings',
+      'default_file' => FEATURES_DEFAULTS_INCLUDED,
+      'feature_source' => TRUE,
+    ),
+  );
 }
 
 /**
  * Implements hook_workflow_operations().
- * Create action link for access form.
+ *
+ * Create action link for access form on EntityWorkflowUIController::overviewForm.
  */
 function workflow_access_workflow_operations($op, $workflow = NULL, $state = NULL) {
   switch ($op) {
     case 'workflow':
-      $alt = t('Control content access for @wf', array('@wf' => $workflow->name));
-      $actions = array(
+      $actions = array();
+      $wid = $workflow->wid;
+
+      $alt = t('Control content access for @wf', array('@wf' => $workflow->getName()));
+      $actions += array(
         'workflow_access_form' => array(
           'title' => t('Access'),
-          'href' => "admin/config/workflow/access/$workflow->wid",
+          'href' => "admin/config/workflow/workflow/manage/$wid/access",
           'attributes' => array('alt' => $alt, 'title' => $alt),
-          ),
-        );
+        ),
+      );
 
       return $actions;
   }
 }
 
 /**
- * Implements hook_form().
+ * Implements hook_node_grants().
  *
- * Add a "three dimensional" (state, role, permission type) configuration
- * interface to the workflow edit form.
+ * Supply the workflow access grants. We are simply using
+ * roles as access lists, so rids translate directly to gids.
  */
-function workflow_access_form($form, $form_state, $workflow) {
-  if ($workflow) {
-    drupal_set_title(t('@name Access', array('@name' => $workflow->name)));
-  }
-  else {
-    drupal_set_message(t('Unknown workflow'));
-    drupal_goto('admin/config/workflow/workflow');
-  }
-
-  $bc = array(l(t('Home'), '<front>'));
-  $bc[] = l(t('Configuration'), 'admin/config');
-  $bc[] = l(t('Workflow'), 'admin/config/workflow');
-  $bc[] = l(t('Workflow'), 'admin/config/workflow/workflow');
-  $bc[] = l(t($workflow->name), "admin/config/workflow/workflow/$workflow->wid");
-//  $bc[] = l(t('Access'), "admin/config/workflow/access/$workflow->wid");
-  drupal_set_breadcrumb($bc);
-
-  $form = array('#tree' => TRUE);
-
-  $form['#wid'] = $workflow->wid;
-
-  // A list of roles available on the site and our
-  // special -1 role used to represent the node author.
-  $rids = user_roles(FALSE, 'participate in workflow');
-
-  $rids['-1'] = t('author');
+function workflow_access_node_grants($account, $op) {
+  return array(
+    'workflow_access' => array_keys($account->roles),
+    'workflow_access_owner' => array($account->uid),
+  );
+}
 
-  // Add a table for every workflow state.
-  $options = array('status' => 1);
-  foreach (workflow_get_workflow_states_by_wid($workflow->wid, $options) as $state) {
-    if ($state->state == t('(creation)')) {
-      // No need to set perms on creation.
-      continue;
-    }
-    $view = $update = $delete = array();
-    $count = 0;
-    foreach (workflow_access_get_workflow_access_by_sid($state->sid) as $access) {
-      $count++;
-      if ($access->grant_view) {
-        $view[] = $access->rid;
+/**
+ * Implements hook_node_access_records().
+ *
+ * Returns a list of grant records for the passed in node object.
+ * This hook is invoked by function node_access_acquire_grants().
+ */
+function workflow_access_node_access_records($node) {
+  $grants = array();
+  $workflow_transitions_added = FALSE;
+
+  // Only relevant for content with Workflow.
+  if (!isset($node->workflow_transitions)) {
+    $node->workflow_transitions = array();
+    // Sometimes, a node is saved without going through workflow_transition_form.
+    // E.g.,
+    // - when saving a node with workflow_node programmatically with node_save();
+    // - when changing a state on a node view page/history tab;
+    // - when rebuilding permissions via batch for workflow_node and workflow_field.
+    // In that case, we need to create the workflow_transitions ourselves to
+    // calculate the grants.
+    foreach (_workflow_info_fields($node, 'node') as $field_name => $field) {
+      $old_sid = FALSE;
+      // Create a dummy transition, just to set $node->workflow_transitions.
+      if (isset($node->workflow)) {
+        $old_sid = $new_sid = $node->workflow;
       }
-      if ($access->grant_update) {
-        $update[] = $access->rid;
+      elseif (isset($node->{$field_name}[LANGUAGE_NONE])) {
+        $old_sid = $new_sid = _workflow_get_sid_by_items($node->{$field_name}[LANGUAGE_NONE]);
       }
-      if ($access->grant_delete) {
-        $delete[] = $access->rid;
+      if ($old_sid) {
+        $transition = new WorkflowTransition();
+        $transition->setValues('node', $node, $field_name, $old_sid, $new_sid, $node->uid, REQUEST_TIME, '');
+
+        // Store the transition, so it can be easily fetched later on.
+        // Store in an array, to prepare for multiple workflow_fields per entity.
+        // This is a.o. used in hook_entity_update to trigger 'transition post'.
+        $node->workflow_transitions[$field_name] = $transition;
+        $workflow_transitions_added = TRUE;
       }
     }
-    // Allow view grants by default for anonymous and authenticated users,
-    // if no grants were set up earlier.
-    if (!$count) {
-      $view = array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID);
-    }
-    // TODO better tables using a #theme function instead of direct #prefixing
-    $form[$state->sid] = array(
-      '#type' => 'fieldset',
-      '#title' => t('@state', array('@state' => $state->state)),
-      '#collapsible' => TRUE,
-      '#collapsed' => FALSE,
-      '#tree' => TRUE,
-      );
-
-    $form[$state->sid]['view'] = array(
-      '#type' => 'checkboxes',
-      '#options' => $rids,
-      '#default_value' => $view,
-      '#title' => t('Roles who can view posts in this state'),
-      '#prefix' => '<table width="100%" style="border: 0;"><tbody style="border: 0;"><tr><td>',
-      );
-
-    $form[$state->sid]['update'] = array(
-      '#type' => 'checkboxes',
-      '#options' => $rids,
-      '#default_value' => $update,
-      '#title' => t('Roles who can edit posts in this state'),
-      '#prefix' => "</td><td>",
-      );
-
-    $form[$state->sid]['delete'] = array(
-      '#type' => 'checkboxes',
-      '#options' => $rids,
-      '#default_value' => $delete,
-      '#title' => t('Roles who can delete posts in this state'),
-      '#prefix' => "</td><td>",
-      '#suffix' => "</td></tr></tbody></table>",
-    );
   }
 
-  $form['warning'] = array(
-    '#type' => 'markup',
-    '#markup' => '<p><strong>'
-      . t('WARNING:')
-      . '</strong> '
-      . t('Use of the "Edit any," "Edit own," and even "View published content" permissions
-        for the content type may override these access settings.')
-      . '</p>',
-      );
-
-  $form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
-
-  return $form;
-  // Place our block comfortably down the page.
-  $form['submit']['#weight'] = 10;
-  $form['#submit'][] = 'workflow_access_form_submit';
-}
+  // Get 'author' of this entity.
+  // - Some entities (e.g., taxonomy_term) do not have a uid.
+  // But then again: node_access is only for nodes...
+  $uid = isset($node->uid) ? $node->uid : 0;
 
-/**
- * Store permission settings for workflow states.
- */
-function workflow_access_form_submit($form, &$form_state) {
-  foreach ($form_state['values'] as $sid => $access) {
-    // Ignore irrelevant keys.
-    if (!is_numeric($sid)) {
+  $count = 0;
+  foreach ($node->workflow_transitions as $transition) {
+    // @todo: add support for +1 workflows per entity.
+    if ($count++ == 1 ) {
       continue;
     }
-    $grants = array();
-    foreach ($access['view'] as $rid => $checked) {
-      $data = array(
-        'sid' => $sid,
-        'rid' => $rid,
-        'grant_view' => (!empty($checked)) ? (bool) $checked : 0,
-        'grant_update' => (!empty($access['update'][$rid])) ? (bool) $access['update'][$rid] : 0,
-        'grant_delete' => (!empty($access['delete'][$rid])) ? (bool) $access['delete'][$rid] : 0,
-      );
-      $id = workflow_access_insert_workflow_access_by_sid($data);
-    }
 
-    // Update all nodes having same workflow state to reflect new settings.
-    foreach (workflow_get_workflow_node_by_sid($sid) as $data) {
-      // Instead of trying to construct what the grants should be per node as we save.
-      // Let's fall back on existing node grant systems that will find it for us.
-      $node = node_load($data->nid);
-      node_access_acquire_grants($node);
+    $field_name = $transition->field_name;
+    $priority = variable_get('workflow_access_priority', 0);
+
+    if ($current_sid = workflow_node_current_state($node, 'node', $field_name)) {
+      foreach (workflow_access_get_workflow_access_by_sid($current_sid) as $grant) {
+        $realm = ($uid > 0 && $grant->rid == WORKFLOW_ROLE_AUTHOR_RID) ? 'workflow_access_owner' : 'workflow_access';
+        $gid = ($uid > 0 && $grant->rid == WORKFLOW_ROLE_AUTHOR_RID) ? $uid : $grant->rid;
+
+        // Anonymous ($uid == 0) author is not allowed for role 'author' (== -1).
+        // Both logically (Anonymous having more rights then authenticated)
+        // and technically: $gid must be a positive int.
+        if ($gid < 0) { // if ($uid == 0 && $grant->rid == WORKFLOW_ROLE_AUTHOR_RID) {
+          continue;
+        }
+
+        $grants[] = array(
+          'realm' => $realm,
+          'gid' => $gid,
+          'grant_view' => $grant->grant_view,
+          'grant_update' => $grant->grant_update,
+          'grant_delete' => $grant->grant_delete,
+          'priority' => $priority,
+          'field_name' => $field_name, // Just for analysis and info.
+        );
+      }
     }
   }
 
-  drupal_set_message(t('Workflow access permissions updated.'));
-  $form_state['redirect'] = 'admin/config/workflow/workflow/' . $form['#wid'];
+  if ($workflow_transitions_added == TRUE) {
+    unset($node->workflow_transitions);
+  }
+
+  return $grants;
 }
 
 /**
- * Implements hook_workflow().
+ * Implements hook_node_access_explain().
  *
- * Update grants when a node changes workflow state.
- * This is already called when node_save is called.
+ * This is a Devel Node Access hook.
  */
-function workflow_access_workflow($op, $old_sid, $sid, $node) {
-  // ALERT:
-  // This is a tricky spot when called on node_insert as part of the transition from create to state1.
-  // node_save invokes this function as a hook before calling node_access_acquire_grants.
-  // But when it calls node_access_acquire_grants later, it does so without deleting the access
-  // set when calling workflow_node_insert because it is an insert and no prior grants are expected.
-  // This leads to a SQL error of duplicate grants which causes a rollback of all changes.
-  // Unfortunately, setting access rights isn't the only thing we're doing on node_insert so we
-  // can't skip the whole thing. So we need to fix it further downstream in order to get this to work.
-  // Here we don't want to run this in the case of (and ONLY in the case of) a brand new node.
-  // Node access grants will be run as part of node_save's own granting after this.
-  //
-  // NOTE: Any module that sets node access rights on insert will hit this situation.
-  //
-  if ($old_state = workflow_get_workflow_states_by_sid($old_sid)) {
-    if ($op == 'transition post' && $old_sid != $sid && (empty($node->is_new) || !$node->is_new)) {
-      node_access_acquire_grants($node);
-    }
+function workflow_access_node_access_explain($row) {
+  static $interpretations = array();
+  switch ($row->realm) {
+    case 'workflow_access_owner':
+      $interpretations[$row->gid] = t('Workflow access: author of the content may access');
+      break;
+
+    case 'workflow_access':
+      $roles = user_roles();
+      $interpretations[$row->gid] = t('Workflow access: %role may access', array('%role' => $roles[$row->gid]));
+      break;
   }
+  return (!empty($interpretations[$row->gid]) ? $interpretations[$row->gid] : NULL);
 }
 
 /**
@@ -281,7 +248,7 @@ function workflow_access_get_workflow_access_by_sid($sid) {
 }
 
 /**
- * Given a sid and a rid (the  unique key), delete all access data for this state.
+ * Given a sid and rid (the unique key), delete all access data for this state.
  */
 function workflow_access_delete_workflow_access_by_sid_rid($sid, $rid) {
   db_delete('workflow_access')->condition('sid', $sid)->condition('rid', $rid)->execute();
@@ -302,57 +269,3 @@ function workflow_access_insert_workflow_access_by_sid(&$data) {
   workflow_access_delete_workflow_access_by_sid_rid($data->sid, $data->rid);
   drupal_write_record('workflow_access', $data);
 }
-
-/**
- * Implements hook_form_alter().
- *
- * Tell the Features module that we intend to provide one exportable component.
- */
-function workflow_access_form_alter(&$form, &$form_state, $form_id) {
-  switch ($form_id) {
-    case 'workflow_admin_ui_types_form':
-      $form['workflow_access'] = array(
-        '#type' => 'fieldset',
-        '#title' => t('Workflow Access'),
-        '#collapsible' => TRUE,
-        '#collapsed' => TRUE,
-        );
-      $form['workflow_access']['workflow_access_priority'] = array(
-        '#type' => 'weight',
-        '#delta' => 10,
-        '#title' => t('Workflow Access Priority'),
-        '#default_value' => variable_get('workflow_access_priority', 0),
-        '#description' => t('This sets the node access priority. This can be dangerous. If there is any doubt,
-          leave it at 0.')
-          . ' '
-          . l(t('Read the manual.'), 'https://api.drupal.org/api/drupal/modules!node!node.api.php/function/hook_node_access_records/7'),
-        );
-       
-      $form['#submit'][] = 'workflow_access_priority_submit';
-      return;
-  }
-}
-
-/**
- * Submit handler.
- */
-function workflow_access_priority_submit($form, &$form_state) {
-  variable_set('workflow_access_priority', $form_state['values']['workflow_access']['workflow_access_priority']);
-}
-
-/**
- * Implements hook_features_api().
- *
- * Tell the Features module that we intend to provide one exportable component.
- */
-function workflow_access_features_api() {
-  return array(
-    'workflow_access' => array(
-      'name' => t('Workflow access'),
-      'file' => drupal_get_path('module', 'workflow_access') . '/workflow_access.features.inc',
-      'default_hook' => 'workflow_access_features_default_settings',
-      'default_file' => FEATURES_DEFAULTS_INCLUDED,
-      'feature_source' => TRUE,
-    ),
-  );
-}

+ 157 - 0
sites/all/modules/contrib/admin/workflow/workflow_access/workflow_access.pages.inc

@@ -0,0 +1,157 @@
+<?php
+/**
+ * @file
+ * Provides pages for administrative UI.
+ */
+
+/**
+ * Implements hook_form().
+ *
+ * Add a form to set the weight fo the access module.
+ */
+function workflow_access_priority_form($form, $form_state) {
+  $form['workflow_access'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Workflow Access Settings'),
+    '#collapsible' => FALSE,
+    '#collapsed' => FALSE,
+  );
+  $form['workflow_access']['#tree'] = TRUE;
+  $form['workflow_access']['workflow_access_priority'] = array(
+    '#type' => 'weight',
+    '#delta' => 10,
+    '#title' => t('Workflow Access Priority'),
+    '#default_value' => variable_get('workflow_access_priority', 0),
+    '#description' => t('This sets the node access priority. Changing this
+      setting can be dangerous. If there is any doubt, leave it at 0. 
+      <a href="@url">Read the manual.</a>', array('@url' => url('https://api.drupal.org/api/drupal/modules!node!node.api.php/function/hook_node_access_records/7'))),
+  );
+  $form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
+
+  return $form;
+}
+
+/**
+ * Submit handler.
+ */
+function workflow_access_priority_form_submit($form, &$form_state) {
+  variable_set('workflow_access_priority', $form_state['values']['workflow_access']['workflow_access_priority']);
+  $form_state['redirect'] = 'admin/config/workflow/workflow';
+}
+
+
+/**
+ * Implements hook_form().
+ *
+ * Add a "three dimensional" (state, role, permission type) configuration
+ * interface to the workflow edit form.
+ */
+function workflow_access_form($form, $form_state, $workflow, $op) {
+  if (!$workflow) {
+    // Leave this page immediately.
+    drupal_set_message(t('Unknown workflow'));
+    drupal_goto('admin/config/workflow/workflow');
+  }
+
+  drupal_set_title(t('@name Access', array('@name' => $workflow->label()))); // No t() for Settings page.
+
+  $form = array('#tree' => TRUE);
+
+  $form['#wid'] = $workflow->wid;
+
+  // A list of role names keyed by role ID, including the 'author' role.
+  $roles = workflow_get_roles('participate in workflow');
+
+  // Add a table for every workflow state.
+  foreach ($workflow->getStates($all = TRUE) as $state) {
+    if ($state->isCreationState()) {
+      // No need to set perms on creation.
+      continue;
+    }
+    $view = $update = $delete = array();
+    $count = 0;
+    foreach (workflow_access_get_workflow_access_by_sid($state->sid) as $access) {
+      $count++;
+      if ($access->grant_view) {
+        $view[] = $access->rid;
+      }
+      if ($access->grant_update) {
+        $update[] = $access->rid;
+      }
+      if ($access->grant_delete) {
+        $delete[] = $access->rid;
+      }
+    }
+    // Allow view grants by default for anonymous and authenticated users,
+    // if no grants were set up earlier.
+    if (!$count) {
+      $view = array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID);
+    }
+    // @todo: better tables using a #theme function instead of direct #prefixing.
+    $form[$state->sid] = array(
+      '#type' => 'fieldset',
+      '#title' => check_plain($state->label()),
+      '#collapsible' => TRUE,
+      '#collapsed' => FALSE,
+      '#tree' => TRUE,
+    );
+
+    $form[$state->sid]['view'] = array(
+      '#type' => 'checkboxes',
+      '#options' => $roles,
+      '#default_value' => $view,
+      '#title' => t('Roles who can view posts in this state'),
+      '#prefix' => '<table width="100%" style="border: 0;"><tbody style="border: 0;"><tr><td>',
+    );
+
+    $form[$state->sid]['update'] = array(
+      '#type' => 'checkboxes',
+      '#options' => $roles,
+      '#default_value' => $update,
+      '#title' => t('Roles who can edit posts in this state'),
+      '#prefix' => "</td><td>",
+    );
+
+    $form[$state->sid]['delete'] = array(
+      '#type' => 'checkboxes',
+      '#options' => $roles,
+      '#default_value' => $delete,
+      '#title' => t('Roles who can delete posts in this state'),
+      '#prefix' => "</td><td>",
+      '#suffix' => "</td></tr></tbody></table>",
+    );
+  }
+
+  $form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
+
+  return $form;
+}
+
+/**
+ * Stores permission settings for workflow states.
+ */
+function workflow_access_form_submit($form, &$form_state) {
+  foreach ($form_state['values'] as $sid => $access) {
+    // Ignore irrelevant keys.
+    if (!is_numeric($sid)) {
+      continue;
+    }
+    foreach ($access['view'] as $rid => $checked) {
+      $data = array(
+        'sid' => $sid,
+        'rid' => $rid,
+        'grant_view' => (!empty($checked)) ? (bool) $checked : 0,
+        'grant_update' => (!empty($access['update'][$rid])) ? (bool) $access['update'][$rid] : 0,
+        'grant_delete' => (!empty($access['delete'][$rid])) ? (bool) $access['delete'][$rid] : 0,
+      );
+      workflow_access_insert_workflow_access_by_sid($data);
+    }
+
+    // Update all nodes having same workflow state to reflect new settings.
+    // just set a flag, which is working for both Workflow Field ánd Workflow Node.
+    node_access_needs_rebuild(TRUE);
+  }
+
+  drupal_set_message(t('Workflow access permissions updated.'));
+  $form_state['redirect'] = 'admin/config/workflow/workflow/' . $form['#wid'];
+}

+ 40 - 0
sites/all/modules/contrib/admin/workflow/workflow_access/workflow_access.workflow.inc

@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Provides node access permissions based on workflow states.
+ */
+
+/**
+ * Implements hook_workflow().
+ *
+ * Update grants when a node changes workflow state.
+ * This is already called when node_save is called.
+ */
+function workflow_access_workflow($op, $id, $new_sid, $entity, $force, $entity_type = '', $field_name = '', $transition = NULL) {
+  // ALERT:
+  // This is a tricky spot when called on node_insert as part of the transition from create to state1.
+  // node_save invokes this function as a hook before calling node_access_acquire_grants.
+  // But when it calls node_access_acquire_grants later, it does so without deleting the access
+  // set when calling workflow_node_insert because it is an insert and no prior grants are expected.
+  // This leads to a SQL error of duplicate grants which causes a rollback of all changes.
+  // Unfortunately, setting access rights isn't the only thing we're doing on node_insert so we
+  // can't skip the whole thing. So we need to fix it further downstream in order to get this to work.
+  // Here we don't want to run this in the case of (and ONLY in the case of) a brand new node.
+  // Node access grants will be run as part of node_save's own granting after this.
+  //
+  // NOTE: Any module that sets node access rights on insert will hit this situation.
+  //
+  switch ($op) {
+    case 'transition post':
+      // This is only used for Workflow Node.
+      // In some other location, we make sure that 'transition post' is only
+      // called for Workflow Node, never for Workflow Field. We do not need
+      // that hook, since you can use the core event 'node_presave' and 'node_save'.
+      // Node_access functions are only for nodes with a state change and a valid new state.
+      if ($entity_type == 'node' && !isset($entity->is_new)) {
+        node_access_acquire_grants($entity);
+      }
+      break;
+  }
+}

+ 37 - 37
sites/all/modules/contrib/admin/workflow/workflow_actions/README.txt

@@ -1,37 +1,37 @@
-
-CONTENTS OF THIS FILE
----------------------
-
- * Introduction
-
-INTRODUCTION
-------------
-
-There is an important issue to keep in mind as you use action hooks and workflow!!
-
-If the machine readable name of the content type on which you want to define actions
-in the workflow exceeds 20 characters then the actions you define will not be visible
-in the screen where you define the triggers nor will they execute.
-
-The reason is that the length of the field "op" in the "trigger-assignments" table
-is 32 characters. The name of this "op"-field is a concatenation of the string
-"workflow-" with the machine readable name of the content type, another "-" and the
-transition-id on which the action has to be performed. If the latter has a length of
-1 then this leaves 32 - 9 - 1 - 1 = 21 characters for the machine readable name of
-the content type.
-
-So: KEEP YOUR CONTENT TYPE NAMES SHORT.
-
-Unfortunately the code that handles this is in core, so not readily changable. If
-you have trouble seeing your actions check your name lengths.
-
-See further discussion at:
-  http://drupal.org/node/585726
-
-See request put to core to make the change at:
-
-  http://drupal.org/node/1062068
-
-  Closed and told to have workflow make the table change ourselves. Given that changing
-  name lengths haphazardly would spread the bugs around even more this approach was
-  not followed.
+
+CONTENTS OF THIS FILE
+---------------------
+
+ * Introduction
+
+INTRODUCTION
+------------
+
+There is an important issue to keep in mind as you use action hooks and workflow!!
+
+If the machine readable name of the content type on which you want to define actions
+in the workflow exceeds 20 characters then the actions you define will not be visible
+in the screen where you define the triggers nor will they execute.
+
+The reason is that the length of the field "op" in the "trigger-assignments" table
+is 32 characters. The name of this "op"-field is a concatenation of the string
+"workflow-" with the machine readable name of the content type, another "-" and the
+transition-id on which the action has to be performed. If the latter has a length of
+1 then this leaves 32 - 9 - 1 - 1 = 21 characters for the machine readable name of
+the content type.
+
+So: KEEP YOUR CONTENT TYPE NAMES SHORT.
+
+Unfortunately the code that handles this is in core, so not readily changeable. If
+you have trouble seeing your actions check your name lengths.
+
+See further discussion at:
+  http://drupal.org/node/585726
+
+See request put to core to make the change at:
+
+  http://drupal.org/node/1062068
+
+  Closed and told to have workflow make the table change ourselves. Given that changing
+  name lengths haphazardly would spread the bugs around even more this approach was
+  not followed.

+ 8 - 5
sites/all/modules/contrib/admin/workflow/workflow_actions/workflow_actions.info

@@ -1,12 +1,15 @@
-name = Workflow actions and triggers
-description = Provides actions and triggers for workflows.
+name = Workflow Trigger
+description = Enables actions to be fired upon a Workflow State change.
+
 dependencies[] = workflow
 dependencies[] = trigger
+
 package = Workflow
 core = 7.x
-; Information added by drupal.org packaging script on 2013-07-04
-version = "7.x-1.2"
+
+; Information added by Drupal.org packaging script on 2017-01-15
+version = "7.x-2.9+10-dev"
 core = "7.x"
 project = "workflow"
-datestamp = "1372980654"
+datestamp = "1484510888"
 

+ 14 - 0
sites/all/modules/contrib/admin/workflow/workflow_actions/workflow_actions.install

@@ -0,0 +1,14 @@
+<?php
+/**
+ * @file
+ * Uninstall functions for the workflow_action module.
+ */
+
+/**
+ * Implements hook_uninstall().
+ */
+function workflow_actions_uninstall() {
+  return db_delete('trigger_assignments')
+    ->condition('hook', 'workflow-%', 'LIKE')
+    ->execute();
+}

+ 260 - 143
sites/all/modules/contrib/admin/workflow/workflow_actions/workflow_actions.module

@@ -1,155 +1,292 @@
 <?php
 /**
  * @file
- * Provide actions and triggers for workflows.
+ * Enables actions to be fired upon a Workflow State change.
+ *
+ * N.B. This module's name is incorrect. It provides Triggers, not Actions.
+ *
  * Why it's own module? Some sites prefer rules, some prefer actions,
  * all prefer a lower code footprint and better performance.
- * Additional creadit to gcassie ( http://drupal.org/user/80260 ) for
+ * Additional credit to gcassie ( http://drupal.org/user/80260 ) for
  * the initial push to split actions out of core workflow.
  */
 
 /**
- * Implements hook_hook_info().
+ * Implements hook_workflow_operations().
+ *
+ * Menu callback, called in workflow.admin.inc to add actions for states.
+ */
+function workflow_actions_workflow_operations($level, $workflow = NULL, $state = NULL) {
+  if ($workflow) {
+    return array(
+      'workflow_overview_actions' => array(
+        'title' => t('Actions'),
+        'href' => 'admin/structure/trigger/workflow/' . $workflow->wid,
+      ),
+    );
+  }
+}
+
+/**
+ * Implements hook_workflow().
+ */
+function workflow_actions_workflow($op, $id, $new_sid, $entity, $force = FALSE, $entity_type = 'node', $field_name = '', $transition = NULL) {
+  switch ($op) {
+    case 'transition post':
+      // Reminder: event 'transition post' does not occur for Workflow Field.
+      _workflow_actions_do($transition);
+      break;
+
+    case 'transition delete':
+      // @todo: implement delete triggers upon 'transition delete'.
+      // The below code generates an error PDOException: SQLSTATE[42S22].
+      // So, it is commented out as per d.o. #2200089. In 7.x-1.2, this trigger
+      // was never invoked. So, 'delete trigger upon transition delete' is now
+      // a feature request.
+      // $tid = $id;
+      // $actions = _workflow_actions_get_actions_by_tid($tid);
+      // foreach ($actions as $aid) {
+      // _workflow_actions_remove($tid, $aid);
+      // }
+      break;
+  }
+}
+
+/**
+ * Implements hook_entity_insert().
+ *
+ * Trigger the 'transition post' event for workflow_field.
+ * Do this only for Trigger & Actions, since Rules has its own way.
+ * And I ndo't like activating an extra hook for nothing.
+ */
+function workflow_actions_entity_insert($entity, $type) {
+  workflow_actions_entity_update($entity, $type);
+}
+
+/**
+ * Implements hook_entity_update().
+ *
+ * @see WorkflowTransition->execute()
+ */
+function workflow_actions_entity_update($entity, $entity_type) {
+  // For workflow_field, the 'transition post' event is not triggered in
+  // WorkflowTransition->execute(), since we are still IN a transition.
+  // This is now triggered here.
+  // (But without hook_workflow, since it crashes workflow_access.)
+  // P.S. You should not mix workflow field and workflow node!!
+  if (module_exists('workflowfield') && !module_exists('workflownode')) {
+    if (isset($entity->workflow_transitions)) {
+      foreach ($entity->workflow_transitions as &$transition) {
+        // $transition->post_execute(); // equivalent with hook_entity_save().
+        _workflow_actions_do($transition);
+      }
+    }
+  }
+  elseif (module_exists('workflownode')) {
+    // This is already done in workflow_actions_workflow(). But we cannot move
+    // that here, since node_save()/entity_save() isn't always triggered.
+  }
+}
+
+/**
+ * Implements hook_trigger_info().
+ *
  * Expose each transition as a hook.
  */
 function workflow_actions_trigger_info() {
-  $states = array();
-  foreach (workflow_get_workflow_states() as $data) {
-    $states[$data->sid] = check_plain($data->state);
+  static $pseudohooks = array();
+
+  if ($pseudohooks) {
+    return $pseudohooks;
   }
-  if (empty($states)) {
-    return array();
+
+  // If we come from a specific workflow, only show triggers for that workflow.
+  // Removed: it is confusing.
+  $trigger_page = FALSE;
+  $wid = 0;
+/*
+  if (drupal_substr($_GET['q'], 0, 32) == 'admin/structure/trigger/workflow') {
+    $trigger_page = TRUE;
+    $wid = arg(4);
   }
-  $trigger_page = drupal_substr($_GET['q'], 0, 32) == 'admin/structure/trigger/workflow';
+ */
+  $workflows = workflow_load_multiple($wid ? array($wid) : FALSE);
 
-  // TODO these should be pulled into their own DB function calls.
-  if ($trigger_page && $wid = arg(4)) {
-    $result = db_query('SELECT tm.type, w.wid, w.name, ws.state, wt.tid, wt.sid, wt.target_sid ' .
-      'FROM {workflow_type_map} tm ' .
-      'LEFT JOIN {workflows} w ON tm.wid = w.wid ' .
-      'LEFT JOIN {workflow_states} ws ON w.wid = ws.wid ' .
-      'LEFT JOIN {workflow_transitions} wt ON ws.sid = wt.sid ' .
-      'WHERE w.wid = :wid AND ws.status = 1 AND wt.target_sid IS NOT NULL ' .
-      'ORDER BY tm.type, ws.weight', array(':wid' => $wid));
+  // Get the mapping for Node API.
+  // Build an array of Wid => array of types.
+  $workflow_node_type_map = module_exists('workflownode') ? workflow_get_workflow_type_map() : array();
+  foreach ($workflow_node_type_map as $type => $wid) {
+    $group = 'workflow';
+    $type_map[$wid][] = array('entity_type' => 'node', 'bundle' => $type, $group);
   }
-  else {
-    $result = db_query('SELECT tm.type, w.wid, w.name, ws.state, wt.tid, wt.sid, wt.target_sid ' .
-    'FROM {workflow_type_map} tm ' .
-    'LEFT JOIN {workflows} w ON tm.wid = w.wid ' .
-    'LEFT JOIN {workflow_states} ws ON w.wid = ws.wid ' .
-    'LEFT JOIN {workflow_transitions} wt ON ws.sid = wt.sid ' .
-    'WHERE ws.status = 1 AND wt.target_sid IS NOT NULL ' .
-    'ORDER BY tm.type, ws.weight');
-  }
-
-  $creation_state = t('(creation)');
-  foreach ($result as $data) {
-    $creation_flag = FALSE;
-    if ($states[$data->sid] == $creation_state) {
-      $creation_flag = TRUE;
+
+  // Get the mapping for Field API.
+  $fields = _workflow_info_fields($entity = NULL, $entity_type = '');
+  foreach ($fields as $field_name => $field) {
+    if ($field['type'] == 'workflow') {
+      foreach ($field['bundles'] as $entity_type => $bundles) {
+        foreach ($bundles as $bundle) {
+          // Add the trigger to the approriate Tab on admin/structure/trigger.
+          switch ($entity_type) {
+            case 'node':          $group = $entity_type; break;
+            case 'taxonomy_term': $group = 'taxonomy';   break;
+            default:              $group = 'workflow';   break;
+          }
+          $type_map[$field['settings']['wid']][] = array(
+            'entity_type' => $entity_type,
+            'bundle' => $bundle,
+            'group' => $group,
+          );
+        }
+      }
+    }
+  }
+
+  // Initialize the Workflow tab on admin/structure/trigger/workflow.
+  $pseudohooks['workflow'] = array();
+  // Create a trigger for each possible combination.
+  foreach ($workflows as $wid => $workflow) {
+    $states = $workflow->getStates('CREATION');
+    foreach ($workflow->getTransitions() as $config_transition) {
+      $state = $states[$config_transition->sid];
+      $target_state = $states[$config_transition->target_sid];
+      if (!$state || !$target_state || !$state->isActive() || !$target_state->isActive()) {
+        continue;
+      }
+      // Add hook for Node API.
+      if (isset($type_map[$wid])) {
+        $creation_flag = $state->isCreationState();
+        foreach ($type_map[$wid] as $type_bundle) {
+
+          // Add the trigger to the appropriate Tab on admin/structure/trigger.
+          $group = isset($type_bundle['group']) ? $type_bundle['group'] : 'workflow';
+
+          $label = t('When @entity_type %bundle moves %workflow from %state to %target_state',
+            array(
+              '@entity_type' => $type_bundle['entity_type'],
+              '%bundle' => $type_bundle['bundle'],
+              '%workflow' => $workflow->label(),
+              '%state' => $state->label(),
+              '%target_state' => $target_state->label(),
+            )
+          );
+          $pseudohooks[$group]['workflow-' . $type_bundle['entity_type'] 
+                                     . '-' . $type_bundle['bundle']
+                                     . '-' . $config_transition->tid] = array(
+            'label' => $label,
+            'workflow_creation_state' => $creation_flag,
+          );
+        }
+      }
     }
-    $pseudohooks['workflow-' . $data->type . '-' . $data->tid] =
-      array('label' => t('When %type moves from %state to %target_state',
-        array('%type' => $data->type, '%state' => $states[$data->sid], '%target_state' => $states[$data->target_sid])),
-        'workflow_creation_state' => $creation_flag,
-      );
   }
 
   // $pseudohooks will not be set if no workflows have been assigned
   // to node types.
-  if (isset($pseudohooks)) {
-    return array(
-      'workflow' => $pseudohooks,
+  if ($pseudohooks) {
+    return $pseudohooks;
+  }
+  elseif ($trigger_page) {
+    drupal_set_message(t('Either no transitions have been set up or this
+      workflow has not yet been assigned to a content type. To enable the
+      assignment of actions, edit the workflow to assign permissions for roles
+      to do transitions. After that is completed, transitions will appear here
+      and you will be able to assign actions to them.')
     );
   }
-  if ($trigger_page) {
-    drupal_set_message(t('Either no transitions have been set up or this workflow has not yet been ' .
-    'assigned to a content type. To enable the assignment of actions, edit the workflow to assign ' .
-    'permissions for roles to do transitions. After that is completed, transitions will appear here ' .
-    'and you will be able to assign actions to them.'));
+  else {
+    return array();
   }
 }
 
 /**
- * Implements hook_workflow().
- *
- * @param $hook
- *   The current workflow operation: 'transition pre' or 'transition post'.
- * @param $old_state
- *   The state ID of the current state.
- * @param  $new_state
- *   The state ID of the new state.
- * @param $node
- *   The node whose workflow state is changing.
- */
-function workflow_actions_workflow($op, $old_state, $new_state, $node) {
-  switch ($op) {
-    case 'transition post':
-      // A transition has occurred; fire off actions associated with this transition.
-      if ($transition = workflow_get_workflow_transitions_by_sid_target_sid($old_state, $new_state) ) {
-        $hook = 'workflow-' . $node->type . '-' . $transition->tid;
-        $aids = trigger_get_assigned_actions($hook);
-        if ($aids) {
-          $context = array(
-          'hook' => $hook,
-          );
-          // We need to get the expected object if the action's type is not 'node'.
-          // We keep the object in $objects so we can reuse it if we have multiple actions
-          // that make changes to an object.
-          foreach ($aids as $aid => $action_info) {
-            if ($action_info['type'] != 'node') {
-              if (!isset($objects[$action_info['type']])) {
-                $objects[$action_info['type']] = _trigger_normalize_node_context($action_info['type'], $node);
-              }
-              // Since we know about the node, we pass that info along to the action.
-              $context['node'] = $node;
-              $result = actions_do($aid, $objects[$action_info['type']], $context);
-            }
-            else {
-              actions_do($aid, $node, $context);
-            }
-          }
-        }
-      }
-    break;
+ * Implements hook_drupal_alter().
+ */
+function workflow_actions_action_info_alter(&$info) {
+  $triggers = workflow_actions_trigger_info();
+  if (empty($triggers)) {
+    return;
+  }
 
-    case 'transition delete':
-      if ($transition = workflow_get_workflow_transitions_by_sid_target_sid($old_state, $new_state) ) {
-        $actions = workflow_actions_get_actions_by_tid($transition->tid);
-        foreach ($actions as $aid) {
-          workflow_actions_remove($transition->tid, $aid);
-        }
+  // Loop through all available node actions and add them as triggers.
+  foreach ($triggers as $groups) {
+    foreach ($groups as $trigger_name => $data) {
+      foreach (node_action_info() as $action => $data) {
+        $info[$action]['triggers'][] = $trigger_name;
       }
-    break;
+    }
   }
 }
 
 /**
- * Implements hook_workflow_operations().
- * Called in workflow.admin.inc to add actions for states.
+ * Helper function, that triggers actions.
+ * 
+ * @param $transition.
+ *   A Workflow Transition object.
  */
-function workflow_actions_workflow_operations($level, $workflow = NULL, $state = NULL) {
-  if ($workflow) {
-    return array('workflow_overview_actions' => array(
-      'title' => t('Actions'),
-      'href' => 'admin/structure/trigger/workflow/' . $workflow->wid),
+function _workflow_actions_do(&$transition) {
+  $entity_type = $transition->entity_type;
+  $entity = $transition->getEntity();
+  $old_sid = $transition->old_sid;
+  $new_sid = $transition->new_sid;
+
+  // A transition occurred; fire off actions associated with this transition.
+  $workflow = $transition->getWorkflow();
+  $config_transitions = $workflow->getTransitionsBySidTargetSid($old_sid, $new_sid);
+  $config_transition = reset($config_transitions);
+  if ($config_transition) {
+    list(, , $entity_bundle) = entity_extract_ids($entity_type, $entity);
+    $hook = 'workflow-' . $entity_type . '-' . $entity_bundle . '-' . $config_transition->tid;
+    $aids = trigger_get_assigned_actions($hook);
+    if (!$aids) {
+      // With 7x.-2.x, the pseudohooks have an extra 'entity_type' in the name.
+      // This is to be backwards compatible with 7.x-1.2
+      $hook = 'workflow-' . $entity_bundle . '-' . $config_transition->tid;
+      $aids = trigger_get_assigned_actions($hook);
+    }
+
+    if ($aids && !isset($transition->workflow_actions_done[$hook])) {
+      // Avoid multiple executions if the entity_save is repeated in an action.
+      $transition->workflow_actions_done[$hook] = TRUE;
+
+      $context = array(
+        'hook' => $hook,
+        'entity_type' => $entity_type,
       );
+      // We need to get the expected object if the action's type is not 'node'.
+      // We keep the object in $objects so we can reuse it if we have
+      // multiple actions that make changes to an object.
+      $objects = array();
+      foreach ($aids as $aid => $action_info) {
+        if ($action_info['type'] != 'node') {
+          if (!isset($objects[$action_info['type']])) {
+            $objects[$action_info['type']] = _trigger_normalize_node_context($action_info['type'], $entity);
+          }
+          // Since we know the node, we pass it along to the action.
+          $context['node'] = $entity;
+          $result = actions_do($aid, $objects[$action_info['type']], $context);
+        }
+        else {
+          actions_do($aid, $entity, $context);
+        }
+      }
+    }
   }
 }
 
+
 /**
  * Remove an action assignment programmatically.
  *
  * Helpful when deleting a workflow.
  *
- * @param $tid
+ * @param int $tid
  *   Transition ID.
- * @param $aid
+ * @param int $aid
  *   Action ID.
  */
-function workflow_actions_remove($tid, $aid) {
-  $ops = array();
-  foreach (workflow_actions_get_trigger_assignments_by_aid($aid) as $data) {
+function _workflow_actions_remove($tid, $aid) {
+  foreach (_workflow_actions_get_trigger_assignments_by_aid($aid) as $data) {
     // Transition ID is the last part, e.g., foo-bar-1.
     $transition = array_pop(explode('-', $data->hook));
     if ($tid == $transition) {
@@ -157,8 +294,8 @@ function workflow_actions_remove($tid, $aid) {
     }
   }
   foreach ($hooks as $hook) {
-    workflow_actions_delete_trigger_assignments_by_aid_op($aid, $hook);
-    foreach (workflow_actions_get_actions_by_aid($aid) as $action) {
+    _workflow_actions_delete_trigger_assignments_by_aid_op($aid, $hook);
+    foreach (_workflow_actions_get_actions_by_aid($aid) as $action) {
       watchdog('workflow', 'Action %action has been unassigned.',
         array('%action' => $action->description));
     }
@@ -170,17 +307,17 @@ function workflow_actions_remove($tid, $aid) {
  */
 
 /**
- * Get all trigger assingments for workflow.
+ * Get all trigger assignments for workflow.
  */
-function workflow_actions_get_trigger_assignments() {
+function _workflow_actions_get_trigger_assignments() {
   $results = db_query('SELECT hook FROM {trigger_assignments} WHERE hook = "workflow"');
   return $results->fetchAll();
 }
 
 /**
- * Get all trigger assignements for workflow and a given action.
+ * Get all trigger assignments for workflow and a given action.
  */
-function workflow_actions_get_trigger_assignments_by_aid($aid) {
+function _workflow_actions_get_trigger_assignments_by_aid($aid) {
   $results = db_query('SELECT hook FROM {trigger_assignments} WHERE hook = "workflow" AND aid = ":aid"', array(':aid' => $aid));
   return $results->fetchAll();
 }
@@ -188,33 +325,34 @@ function workflow_actions_get_trigger_assignments_by_aid($aid) {
 /**
  * Delete assignments, by action and operation.
  */
-function workflow_actions_delete_trigger_assignments_by_aid_op($aid, $op) {
+function _workflow_actions_delete_trigger_assignments_by_aid_op($aid, $op) {
   return db_delete('trigger_assignments')->condition('hook', 'workflow')->condition('hook', $op)->condition('aid', $aid)->execute();
 }
 
 /**
  * Get a specific action.
  */
-function workflow_actions_get_actions_by_aid($aid) {
+function _workflow_actions_get_actions_by_aid($aid) {
   $results = db_query('SELECT * FROM {actions} WHERE aid = ":aid"', array(':aid' => $aid));
   return $results->fetchAll();
 }
 
 /**
- * Get the actions associated with a given transition.
+ * Gets the actions associated with a given transition.
+ *
  * Array of action ids in the same format as _trigger_get_hook_aids().
  */
-function workflow_actions_get_actions_by_tid($tid) {
+function _workflow_actions_get_actions_by_tid($tid) {
   $aids = array();
-  foreach (workflow_actions_get_trigger_assignments() as $data) {
+  foreach (_workflow_actions_get_trigger_assignments() as $data) {
     // Transition ID is the last part, e.g., foo-bar-1.
     $transition = array_pop(explode('-', $data->hook));
     if ($tid == $transition) {
-      // Specialized, TODO seprate this SQL out later
-      $results = db_query('SELECT aa.aid, a.type FROM {trigger_assignments} aa ' .
-        'LEFT JOIN {actions} a ON aa.aid = a.aid ' .
-        'WHERE aa.hook = ":hook" ' .
-        'ORDER BY weight', array(':hook' => $data->hook));
+      // Specialized, TODO separate this SQL out later.
+      $results = db_query('SELECT aa.aid, a.type FROM {trigger_assignments} aa
+        LEFT JOIN {actions} a ON aa.aid = a.aid
+        WHERE aa.hook = ":hook"
+        ORDER BY weight', array(':hook' => $data->hook));
       foreach ($results as $action) {
         $aids[$action->aid]['type'] = $action->type;
       }
@@ -222,24 +360,3 @@ function workflow_actions_get_actions_by_tid($tid) {
   }
   return $aids;
 }
-
-/**
- * Implementation of hook_drupal_alter().
- */
-function workflow_actions_action_info_alter(&$info) {
-  $transitions = workflow_actions_trigger_info();
-  if (empty($transitions)) {
-    return;
-  }
-  foreach ((array)$transitions['workflow'] as $transition => $data) {
-    // Loop through all available node actions and add them as triggers.
-    // But not if this has been flagged as a creation state.
-    if ($data['workflow_creation_state'] != TRUE) {
-      foreach (node_action_info() as $action => $data) {
-        $info[$action]['triggers'][] = $transition;
-      }
-    }
-    // Either way, unset the creation flag so we don't confuse anyone later.
-    unset($transitions['workflow'][$transition]['workflow_creation_state']);
-  }
-}

+ 167 - 0
sites/all/modules/contrib/admin/workflow/workflow_admin_ui/includes/Entity/EntityWorkflowUIController.php

@@ -0,0 +1,167 @@
+<?php
+
+/**
+ * @file
+ * Contains workflow_admin_ui\includes\Entity\EntityWorkflowUIController.
+ */
+
+class EntityWorkflowUIController extends EntityDefaultUIController {
+  /**
+   * Provides definitions for implementing hook_menu().
+   */
+  public function hook_menu() {
+    $items = parent::hook_menu();
+
+    // Set this on the object so classes that extend hook_menu() can use it.
+    $id_count = count(explode('/', $this->path));
+    $wildcard = isset($this->entityInfo['admin ui']['menu wildcard']) ? $this->entityInfo['admin ui']['menu wildcard'] : '%entity_object';
+    $plural_label = isset($this->entityInfo['plural label']) ? $this->entityInfo['plural label'] : $this->entityInfo['label'] . 's';
+    $entityType = $this->entityInfo['entity class'];
+
+    // @todo: Allow modules to insert their own action links to the 'workflow',
+    // $workflow_operations = module_invoke_all('workflow_operations', 'workflow', NULL);
+
+    $item = array(
+      'file path' => isset($this->entityInfo['admin ui']['file path']) ? $this->entityInfo['admin ui']['file path'] : drupal_get_path('module', $this->entityInfo['module']),
+      'access arguments' => array('administer workflow'),
+      'type' => MENU_LOCAL_TASK,
+    );
+
+    $items[$this->path . '/manage/' . $wildcard . '/states'] = $item + array(
+      'file' => 'workflow_admin_ui/workflow_admin_ui.page.states.inc',
+      'title' => 'States',
+      'weight' => '11',
+      'page callback' => 'drupal_get_form',
+      'page arguments' => array('workflow_admin_ui_states_form', $id_count + 1, $id_count + 2),
+    );
+
+    $items[$this->path . '/manage/' . $wildcard . '/transitions'] = $item + array(
+      'file' => 'workflow_admin_ui/workflow_admin_ui.page.transitions.inc',
+      'title' => 'Transitions',
+      'weight' => '12',
+      'page callback' => 'drupal_get_form',
+      'page arguments' => array('workflow_admin_ui_transitions_form', $id_count + 1, $id_count + 2),
+    );
+
+    $items[$this->path . '/manage/' . $wildcard . '/labels'] = $item + array(
+      'file' => 'workflow_admin_ui/workflow_admin_ui.page.labels.inc',
+      'title' => 'Labels',
+      'weight' => '13',
+      'page callback' => 'drupal_get_form',
+      'page arguments' => array('workflow_admin_ui_labels_form', $id_count + 1, $id_count + 2),
+    );
+
+    $items[$this->path . '/manage/' . $wildcard . '/permissions'] = $item + array(
+      'file' => 'workflow_admin_ui/workflow_admin_ui.page.permissions.inc',
+      'title' => 'Permission summary',
+      'weight' => '14',
+      'page callback' => 'workflow_admin_ui_view_permissions_form',
+      'page arguments' => array($id_count + 1, $id_count + 2),
+      // @todo: convert to drupal_get_form('workflow_admin_ui_view_permissions_form');
+      // 'page callback' => 'drupal_get_form',
+      // 'page arguments' => array('workflow_admin_ui_view_permissions_form', $id_count + 1, $id_count + 2),
+    );
+
+    return $items;
+  }
+
+  protected function operationCount() {
+    // Add more then enough colspan.
+    return parent::operationCount() + 8;
+  }
+
+/*
+  public function operationForm($form, &$form_state, $entity, $op) {}
+ */
+
+  public function overviewForm($form, &$form_state) {
+    // Add table and pager.
+    $form = parent::overviewForm($form, $form_state);
+
+    // Allow modules to insert their own action links to the 'table', like cleanup module.
+    $top_actions = module_invoke_all('workflow_operations', 'top_actions', NULL);
+
+    // Allow modules to insert their own workflow operations.
+    foreach ($form['table']['#rows'] as &$row) {
+      $url = $row[0]['data']['#url'];
+      $workflow = $url['options']['entity'];
+      foreach ($actions = module_invoke_all('workflow_operations', 'workflow', $workflow) as $action) {
+        $action['attributes'] = isset($action['attributes']) ? $action['attributes'] : array();
+        $row[] = l(strtolower($action['title']), $action['href'], $action['attributes']);
+      }
+    }
+
+    // @todo: add these top actions next to the core 'Add workflow' action.
+    $top_actions_args = array(
+      'links' => $top_actions,
+      'attributes' => array('class' => array('inline', 'action-links')),
+    );
+
+    $form['action-links'] = array(
+      '#type' => 'markup',
+      '#markup' => theme('links', $top_actions_args),
+      '#weight' => -1,
+    );
+
+    if (module_exists('workflownode')) {
+      // Append the type_map form, changing the form by reference.
+      // The 'type_map' form is only valid for Workflow Node API.
+      module_load_include('inc', 'workflow_admin_ui', 'workflow_admin_ui.page.type_map');
+      workflow_admin_ui_type_map_form($form);
+    }
+
+    // Add a submit button. The submit functions are added in the sub-forms.
+    $form['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Save'),
+      '#weight' => 100,
+    );
+
+    return $form;
+  }
+  /*
+   * Avoids the 'Delete' action if the Workflow is used somewhere.
+   */
+  protected function overviewTableRow($conditions, $id, $entity, $additional_cols = array()) {
+    // Avoid the 'delete' operation if the Workflow is used somewhere.
+    $status = $entity->status;
+
+    // @see parent::overviewTableRow() how to determine a deletable entity.
+    if (!entity_has_status($this->entityType, $entity, ENTITY_IN_CODE) && !$entity->isDeletable())  {
+      // Set to a state that does not allow deleting, but allows other actions.
+      $entity->status = ENTITY_IN_CODE;
+    }
+    $row = parent::overviewTableRow($conditions, $id, $entity, $additional_cols);
+
+    // Just to be sure: reset status.
+    $entity->status = $status;
+
+    return $row;
+  }
+
+  /**
+   * Overrides the 'revert' action, to not delete the workflows.
+   *
+   * @see https://www.drupal.org/node/2051079
+   * @see https://www.drupal.org/node/1043634
+   */
+  public function applyOperation($op, $entity) {
+    $label = entity_label($this->entityType, $entity);
+    $vars = array('%entity' => $this->entityInfo['label'], '%label' => $label);
+    $id = entity_id($this->entityType, $entity);
+    $edit_link = l(t('edit'), $this->path . '/manage/' . $id . '/edit');
+
+    switch ($op) {
+      case 'revert':
+        $defaults = workflow_get_defaults($entity->module);
+        workflow_revert($defaults, $entity->getName());
+        watchdog($this->entityType, 'Reverted %entity %label to the defaults.', $vars, WATCHDOG_NOTICE, $edit_link);
+        return t('Reverted %entity %label to the defaults.', $vars);
+
+      case 'delete':
+      case 'import':
+      default:
+        return parent::applyOperation($op, $entity);
+    }
+  }
+}

+ 44 - 0
sites/all/modules/contrib/admin/workflow/workflow_admin_ui/includes/Test/workflow_named_transitions.test

@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Tests for the Workflow Named Transitions module.
+ */
+
+class WorkflowNamedTransitionsTest extends DrupalWebTestCase {
+  function getInfo() {
+    return array(
+      'name' => 'Workflow Named Transitions Test',
+      'description' => "This tests whether the Edit Labels tab exists in the workflow area.",
+      'group' => 'Workflow',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('workflow', 'workflow_named_transitions');
+  }
+
+  function testEditLabelsExistsAdmin() {
+    $user = $this->drupalCreateUser(array('administer workflow'));
+    $this->drupalLogin($user);
+    $this->drupalGet('admin/build/workflow');
+    // HTML of the Edit labels tab.
+    $this->assertRaw(sprintf('admin/build/workflow/labels">%s</a>', t('Edit labels')), t('Edit labels tab found'));
+    $this->drupalGet('admin/build/workflow/labels');
+    $this->assertResponse('200');
+  }
+
+  function testEditLabelsMissing() {
+    $user = $this->drupalCreateUser(array('access content'));
+    $this->drupalLogin($user);
+    $this->drupalGet('admin/build/workflow');
+    // HTML of the Edit labels tab
+    $this->assertNoRaw(sprintf('admin/build/workflow/labels">%s</a>', t('Edit labels')), t('Edit labels tab not found'));
+    $this->drupalGet('admin/build/workflow/labels');
+    $this->assertResponse('403');
+  }
+
+  function tearDown() {
+    parent::tearDown();
+  }
+}

+ 11 - 4
sites/all/modules/contrib/admin/workflow/workflow_admin_ui/workflow_admin_ui.api.php

@@ -7,16 +7,18 @@
 /**
  * Implements hook_workflow_operations().
  *
- * @param $op
+ * @param string $op
  *   'top_actions': Allow modules to insert their own front page action links.
  *   'operations': Allow modules to insert their own workflow operations.
  *   'state':  Allow modules to insert state operations.
- * @param $workflow
+ * @param object $workflow
  *   The current workflow object.
- * @param $state
+ * @param object $state
  *   The current state object.
+ *
+ * @return array
  */
-function hook_workflow($op, object $workflow, object $state) {
+function hook_workflow_operations($op, object $workflow, object $state) {
   switch ($op) {
     case 'top_actions':
       $actions = array();
@@ -32,6 +34,11 @@ function hook_workflow($op, object $workflow, object $state) {
       // Your module may add to these actions.
       return $actions;
 
+    case 'workflow':
+      $actions = array();
+      // Allow modules to insert their own workflow operations.
+      return $actions;
+
     case 'state':
       $ops = array();
       // The workflow_admin_ui module does not use this.

+ 4 - 4
sites/all/modules/contrib/admin/workflow/workflow_admin_ui/workflow_admin_ui.css

@@ -3,17 +3,17 @@
  * Style Sheets for the Workflow_Admin_UI module.
  */
  
-#workflow_admin_ui_overview .state-name input {
+#workflow_admin_ui_states .state-name input {
   margin-right: 2em; /* Make room for dragging handle. */
 }
 
-#workflow_admin_ui_overview .state-status {
+#workflow_admin_ui_states .state-status {
   text-align: center;
 }
 
-#workflow_admin_ui_overview .state-count {
+#workflow_admin_ui_states .state-count {
   text-align: right;
 }
 
-#workflow_admin_ui_overview th.state-ops {
+#workflow_admin_ui_states th.state-ops {
 }

+ 7 - 4
sites/all/modules/contrib/admin/workflow/workflow_admin_ui/workflow_admin_ui.info

@@ -1,14 +1,17 @@
 name = Workflow UI
 description = "Provides administrative UI for workflow."
 package = Workflow
-dependencies[] = workflow
 core = 7.x
+
 configure = admin/config/workflow/workflow
+
+dependencies[] = workflow
+files[] = includes/Entity/EntityWorkflowUIController.php
 stylesheets[all][] = workflow_admin_ui.css
 
-; Information added by drupal.org packaging script on 2013-07-04
-version = "7.x-1.2"
+; Information added by Drupal.org packaging script on 2017-01-15
+version = "7.x-2.9+10-dev"
 core = "7.x"
 project = "workflow"
-datestamp = "1372980654"
+datestamp = "1484510888"
 

+ 10 - 16
sites/all/modules/contrib/admin/workflow/workflow_admin_ui/workflow_admin_ui.install

@@ -1,8 +1,8 @@
 <?php
+
 /**
  * @file
  * Install, update and uninstall functions for the workflow_admin_ui module.
- *
  */
 
 /**
@@ -16,25 +16,19 @@ function workflow_admin_ui_enable() {
  * Helper function. Used both by update and enable.
  */
 function _workflow_admin_ui_participate() {
-  $perms = array('participate in workflow' => 1);
-  foreach (user_roles() as $rid => $name) {
-    user_role_change_permissions($rid, $perms);
-  }
-  return t('Please review which roles may participate in workflows. <a href="!url">Permissions</a>',
-    array('!url' => url('admin/people/permissions', array('fragment' => 'module-workflow_admin_ui'))));
+  return t("Please review which roles may 'participate in workflows' <a href='!url'>on the Permissions page</a>.",
+    array('!url' => url('admin/people/permissions', array('fragment' => 'module-workflow'))));
 }
 
 /**
- * Patch for #1540824 requires that the menu be rebuilt.
- */
-function workflow_admin_ui_update_7000(&$sandbox) {
-  menu_rebuild();
-  return t('Workflow_admin_ui requested a menu rebuild.');
-}
-
-/**
- * Give all user roles the ability to participate in workflows.
+ * Gives all user roles the ability to participate in workflows.
+ *
+ * This is only done for updating. New installs must set the roles themselves.
  */
 function workflow_admin_ui_update_7001(&$sandbox) {
+  $perms = array('participate in workflow' => 1);
+  foreach (user_roles() as $rid => $name) {
+    user_role_change_permissions($rid, $perms);
+  }
   return _workflow_admin_ui_participate();
 }

+ 112 - 1307
sites/all/modules/contrib/admin/workflow/workflow_admin_ui/workflow_admin_ui.module

@@ -1,28 +1,92 @@
 <?php
+
 /**
  * @file
  * Provides administrative UI for workflow.
+ *
  * Why it's own module? Lower code footprint and better performance.
- * Additional creadit to gcassie ( http://drupal.org/user/80260 ) for
+ * Additional credit to gcassie ( http://drupal.org/user/80260 ) for
  * the initial push to split UI out of core workflow.
  * We're moving workflow in a API direction, so UI and the like - out.
  */
 
-define('WORKFLOW_ARROW', '&#8594;');
+// Caveat: Several hooks have moved into the EntityWorkflowUIController class.
+define('WORKFLOW_ADMIN_UI_ARROW', '&#8594;');
+
+/**
+ * Implements hook_entity_info_alter().
+ *
+ * Adds Admin UI to entities, using EntityWorkflowUIController.
+ */
+function workflow_admin_ui_entity_info_alter(&$entity_info) {
+  $entity_info['Workflow'] += array(
+    'access callback' => 'workflow_access',
+    'admin ui' => array(
+      'path' => 'admin/config/workflow/workflow',
+      // Do not add 'file', since each page has its own file.
+      // 'file' => 'workflow_admin_ui/workflow_admin_ui.pages.inc',
+      'controller class' => 'EntityWorkflowUIController',
+      'menu wildcard' => '%workflow',
+    ),
+  );
+}
 
 /**
  * Implements hook_help().
  */
 function workflow_admin_ui_help($path, $arg) {
   switch ($path) {
-    case 'admin/config/workflow/workflow/edit/%':
-      return t('You are currently viewing the possible transitions to and from workflow states. The state is shown in the left column; ' .
-      'the state to be moved to is to the right. For each transition, check the box next to the role(s) that may initiate the transition. ' .
-      'For example, if only the "production editor" role may move a node from Review state to the Published state, check the box next to ' .
-      '"production editor". The author role is built in and refers to the user who authored the node.');
+
+    case 'admin/modules':
+    case 'admin/config/workflow/workflow':
+    // case 'admin/config/workflow/workflow/manage/%/states': // Already has its own text.
+      if (module_exists('workflownode') && module_exists('workflowfield')) {
+        $m = t('Do not enable Workfow Node and Workflow Field submodules at the
+          same time (unless you are in a migration phase). Visit the <a href=
+          "@url">modules</a> page.', array('@url' => url('admin/modules',
+          array('fragment' => 'Workflow'))));
+        drupal_set_message($m, 'warning');
+      }
+      return;
+
     case 'admin/config/workflow/workflow/add':
-      return t('To get started, provide a name for your workflow. This name will be used as a label when the workflow status is shown ' .
-      'during node editing.');
+      return t('To get started, provide a name for your workflow. This name
+        will be used as a label when the workflow status is shown during node
+        editing.');
+
+    case 'admin/config/workflow/workflow/manage/%/states':
+      return t("To create a new state, enter its name in the last row of the
+        'State' column. Check the 'Active' box to make it effective. You may
+        also drag it to the appropriate position.") . '<br />'
+      . t("A state must be marked as active, to be available in the
+        workflow's transitions.") . '<br />'
+      . t("If you wish to inactivate a state that has content (i.e. count is
+        not zero), then you need to select a state to which to reassign that
+        content.");
+
+    case 'admin/config/workflow/workflow/manage/%/transitions':
+      return t('You are currently viewing the possible transitions to and from
+        workflow states. The state is shown in the left column; the state to be
+        moved to is to the right. For each transition, check the box next to
+        the role(s) that may initiate the transition. For example, if only the
+        "production editor" role may move a node from Review state to the
+        Published state, check the box next to "production editor". The author
+        role is built in and refers to the user who authored the node.')
+      . '<br /><i>'
+      . t("If not all roles are in the list, please review which roles may
+        'participate in workflows' <a href='!url'> on the Permissions page</a>.
+        </i>",
+        array('!url' => url('admin/people/permissions', array(
+          'fragment' => 'module-workflow'))));
+
+    case 'admin/config/workflow/workflow/manage/%/labels':
+      return t('You can add labels to transitions if you don\'t like the
+        standard state labels. They will modify the Workflow form options, so
+        specific workflow transitions can have their own labels, relative to
+        the beginning and ending states. Rather than showing the user a
+        workflow box containing options like "review required" as a state in
+        the workflow, it could say "move to the editing department for grammar
+        review".');
   }
 }
 
@@ -34,1334 +98,75 @@ function workflow_admin_ui_permission() {
     'administer workflow' => array(
       'title' => t('Administer workflow'),
       'description' => t('Administer workflow configurations.'),
-      ),
-    'participate in workflow' => array(
-      'title' => t('Participate in workflow'),
-      'description' => t('Role is shown on workflow admin pages.'),
-      ),
+    ),
   );
 }
 
-/**
- * Implements hook_user_role_insert().
- * Make sure new roles are allowed to participate in workflows by default.
- */
-function workflow_admin_ui_user_role_insert($role) {
-  user_role_change_permissions($role->rid, array('participate in workflow' => 1));
-}
-
-/**
- * Implements hook_menu().
- */
-function workflow_admin_ui_menu() {
-  $items['admin/config/workflow/workflow'] = array(
-    'title' => 'Workflow',
-    'access arguments' => array('administer workflow'),
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('workflow_admin_ui_types_form'),
-    'description' => 'Allows the creation and assignment of arbitrary workflows to node types.',
-    );
-
-  $items['admin/config/workflow/workflow/%workflow'] = array(
-    'access arguments' => array('administer workflow'),
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('workflow_admin_ui_overview_form', 4),
-    'type' => MENU_CALLBACK,
-    );
-
-  $items['admin/config/workflow/workflow/add'] = array(
-    'access arguments' => array('administer workflow'),
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('workflow_admin_ui_add_form'),
-    'type' => MENU_CALLBACK,
-    );
-
-  $items['admin/config/workflow/workflow/edit/%workflow'] = array(
-    'title' => 'Edit',
-    'access arguments' => array('administer workflow'),
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('workflow_admin_ui_edit_form', 5),
-    'type' => MENU_CALLBACK,
-    );
-
-  $items["admin/config/workflow/workflow/delete/%workflow"] = array(
-    'title' => 'Delete',
-    'access arguments' => array('administer workflow'),
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('workflow_admin_ui_delete_form',5),
-    'type' => MENU_CALLBACK,
-    );
-
-  $items["admin/config/workflow/workflow/transitions/%workflow"] = array(
-    'title' => 'Transitions',
-    'access arguments' => array('administer workflow'),
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('workflow_admin_ui_transitions_form',5),
-    'type' => MENU_CALLBACK,
-    );
-
-  $items["admin/config/workflow/workflow/perm_summary/%workflow"] = array(
-    'title' => 'Permission Summary',
-    'access arguments' => array('administer workflow'),
-    'page callback' => 'workflow_admin_ui_view_permissions',
-    'page arguments' => array(5),
-    'type' => MENU_CALLBACK,
-    );
-
-  return $items;
-}
-
-/**
- * Implements hook_theme().
- */
-function workflow_admin_ui_theme() {
-  return array(
-    'workflow_admin_ui_transitions_form' => array('render element' => 'form'),
-    'workflow_admin_ui_edit_form' => array('render element' => 'form'),
-    'workflow_admin_ui_types_form' => array('render element' => 'form'),
-    'workflow_admin_ui_overview_form' => array('render element' => 'form'),
-  );
-}
-
-/**
- * Form builder. Create the form for adding/editing a workflow.
- *
- * @param $name
- *   Name of the workflow if editing.
- * @param $add
- *   Boolean, if true edit workflow name.
- *
- * @return
- *   HTML form.
- */
-function workflow_admin_ui_add_form($form, &$form_state, $name = NULL) {
-  $form = array();
-
-  $form['wf_name'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Workflow Name'),
-    '#maxlength' => '254',
-    '#required' => TRUE,
-    '#default_value' => $name,
-    );
-
-  $form['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Add Workflow'),
-    );
-
-  return $form;
-}
-
-/**
- * Validate the workflow add form.
- *
- * @see workflow_add_form()
- */
-function workflow_admin_ui_add_form_validate($form, &$form_state) {
-  $workflow_name = $form_state['values']['wf_name'];
-
-  // Make sure workflow name is not a duplicate.
-  $workflows = array();
-  foreach (workflow_get_workflows() as $data) {
-    $workflows[$data->wid] = check_plain(t($data->name));
-  }
-  if (!empty($workflows)) {
-    $workflows = array_flip($workflows);
-    if (array_key_exists($workflow_name, $workflows)) {
-      form_set_error('wf_name', t('A workflow with the name %name already exists. Please enter another name for your new workflow.',
-        array('%name' => $workflow_name)));
-    }
-  }
-}
-
-/**
- * Submit handler for the workflow add form.
- *
- * @see workflow_add_form()
- */
-function workflow_admin_ui_add_form_submit($form, &$form_state) {
-  $workflow_name = $form_state['values']['wf_name'];
-
-  $workflow = array(
-    'name' => $workflow_name,
-    'tab_roles' => '',
-    'options' => serialize(array()),
-    );
-
-  workflow_update_workflows($workflow);
+function workflow_form($form, &$form_state, $workflow, $op, $entity_type) {
+  module_load_include('inc', 'workflow_admin_ui', 'workflow_admin_ui.page.workflow');
 
-  watchdog('workflow', 'Created workflow %name', array('%name' => $workflow->name));
-  drupal_set_message(t('The workflow %name was created. You should set the options for your workflow.',
-    array('%name' => $workflow->name)), 'status');
-
-  $form_state['wid'] = $workflow->wid;
-  $form_state['redirect'] = 'admin/config/workflow/workflow/edit/' . $workflow->wid;
-}
-
-/**
- * Form builder. Create form for confirmation of workflow deletion.
- *
- * @param $wid
- *   The workflow object to delete.
- * @return
- *   Form definition array.
- *
- */
-function workflow_admin_ui_delete_form($form, &$form_state, $workflow) {
-  // If we don't have a workflow that goes with this, return to the admin pg.
-  if ($workflow) {
-    // Let's add some breadcrumbs.
-    workflow_admin_ui_breadcrumbs($workflow);
-
-    $form['wid'] = array('#type' => 'value', '#value' => $workflow->wid);
+  switch ($op) {
+    case 'add':
+    case 'edit':
+    case 'clone':
+      return workflow_admin_ui_edit_form($form, $form_state, $workflow, $op);
 
-    return confirm_form(
-      $form,
-      t('Are you sure you want to delete %title? All nodes that have a workflow state associated with this workflow will ' .
-        'have those workflow states removed.', array('%title' => $workflow->name)),
-      !empty($_GET['destination']) ? $_GET['destination'] : 'admin/config/workflow/workflow',
-      t('This action cannot be undone.'),
-      t('Delete ' . $workflow->name),   // This seems to get check_plain'ed.
-      t('Cancel')
-      );
-  }
-  else {
-    drupal_goto('admin/config/workflow/workflow');
+    case 'view':
+    case 'delete':
+    default:
   }
 }
 
 /**
- * Submit handler for workflow deletion form.
+ * Determines whether the given user has access to a Workflow entity.
  *
- * @see workflow_delete_form()
- */
-function workflow_admin_ui_delete_form_submit($form, &$form_state) {
-  if ($form_state['values']['confirm'] == 1
-    && $workflow = workflow_get_workflows_by_wid($form_state['values']['wid'])) {
-    workflow_delete_workflows_by_wid($form_state['values']['wid']);
-
-    watchdog('workflow', 'Deleted workflow %name with all its states', array('%name' => $workflow->name));
-
-    drupal_set_message(t('The workflow %name with all its states was deleted.', array('%name' => $workflow->name)));
-
-    $form_state['redirect'] = 'admin/config/workflow/workflow';
-  }
-}
-
-/**
- * View workflow permissions by role
+ * @param string $op
+ *   The operation being performed. One of 'view', 'update', 'create' or 'delete'.
+ * @param object $entity
+ *   Entity to check access for. If no entity is given, it will be
+ *   determined whether access is allowed for all entities of the given type.
+ * @param object $account
+ *   The user to check for. Leave it to NULL to check for the global user.
+ * @param string $entity_type
+ *   The entity type.
  *
- * @param $workflow
- *   The workflow object.
+ * @return bool
+ *   Whether access is allowed or not. If the entity type does not specify any
+ *   access information, NULL is returned.
  */
-function workflow_admin_ui_view_permissions($workflow) {
-  // If we don't have a workflow at this point, go back to admin pg.
-  if ($workflow) {
-    // Place some breadcrumbs.
-    workflow_admin_ui_breadcrumbs($workflow);
-
-    $name = $workflow->name;
-    $all = array();
-
-    $roles = workflow_admin_ui_get_roles();
-    foreach ($roles as $role => $value) {
-      $all[$role]['name'] = $value;
-    }
-
-    // TODO return to this, this looks similar to actions stuff (transitions) - should be it's own function.
-    $result = db_query(
-      'SELECT t.roles, s1.state AS state_name, s2.state AS target_state_name ' .
-      'FROM {workflow_transitions} t ' .
-      'INNER JOIN {workflow_states} s1 ON s1.sid = t.sid ' .
-      'INNER JOIN {workflow_states} s2 ON s2.sid = t.target_sid ' .
-      'WHERE s1.wid = :wid ' .
-      'ORDER BY s1.weight ASC , s1.state ASC , s2.weight ASC , s2.state ASC',
-      array(':wid' => $workflow->wid));
-    foreach ($result as $data) {
-      foreach (explode(',', $data->roles) as $role) {
-        $all[$role]['transitions'][] = array(check_plain(t($data->state_name)), WORKFLOW_ARROW, check_plain(t($data->target_state_name)));
-      }
-    }
-
-    $header = array(t('From'), '', t('To'));
-    $output = '';
-
-    // TODO we should theme out the html here.
-    foreach ($all as $role => $value) {
-      if (!empty($value['name'])) {
-        $output .= '<h3>' . t('%role may do these transitions:', array('%role' => $value['name'])) . '</h3>';
-      }
-      if (!empty($value['transitions'])) {
-        $output .= theme('table', array('header' => $header, 'rows' => $value['transitions'])) . '<p></p>';
-      }
-      else {
-        $output .= '<table><tbody><tr class="odd"><td>' . t('None') . '</td><td></tr></tbody></table><p></p>';
-      }
-    }
-
-    return $output;
-  }
-  else {
-    drupal_goto('admin/config/workflow/workflow');
-  }
+function workflow_access($op, $entity, $account, $entity_type) {
+  return user_access('administer workflow', $account);
 }
 
 /**
- * Theme the workflow permissions view.
+ * Implements hook_theme().
  */
-function theme_workflow_admin_ui_view_permissions($variables) {
-  $header = $variables['header'];
-  $all = $variables['all'];
-  $output = '';
-
-  foreach ($all as $role => $value) {
-    if (!empty($value['name'])) {
-      $output .= '<h3>' . t('%role may do these transitions:', array('%role' => $value['name'])) . '</h3>';
-    }
-    if (!empty($value['transitions'])) {
-      $output .= theme('table', array('header' => $header, 'rows' => $value['transitions'])) . '<p></p>';
-    }
-    else {
-      $output .= '<table><tbody><tr class="odd"><td>' . t('None') . '</td><td></tr></tbody></table><p></p>';
-    }
-  }
-
-  return $output;
+function workflow_admin_ui_theme() {
+  return array(
+    'workflow_admin_ui_type_map_form' => array('render element' => 'form'),
+    'workflow_admin_ui_states_form' => array('render element' => 'form'),
+    'workflow_admin_ui_transitions_form' => array('render element' => 'form'),
+  );
 }
 
 /**
  * Helper function. Create breadcrumbs.
  *
- * @param $workflow
+ * @param object $workflow
  *   The workflow object.
- * @param $extra (optional)
- *   The link to the extra item to add to the end of the breadcrumbs.
- * @return
- *   none.
+ * @param mixed $extra
+ *   Optional. The link to the extra item to add to the end of the breadcrumbs.
  */
 function workflow_admin_ui_breadcrumbs($workflow, $extra = NULL) {
   $bc = array(l(t('Home'), '<front>'));
   $bc[] = l(t('Configuration'), 'admin/config');
   $bc[] = l(t('Workflow'), 'admin/config/workflow');
   $bc[] = l(t('Workflow'), 'admin/config/workflow/workflow');
-  $bc[] = l(t($workflow->name), "admin/config/workflow/workflow/$workflow->wid");
-  if ($extra) {
-    $bc[] = $extra;
-  }
-  drupal_set_breadcrumb($bc);
-}
-
-/**
- * Menu callback. Edit a workflow's properties.
- *
- * @param $wid
- *   The workflow object..
- * @return
- *   HTML form and permissions table.
- */
-function workflow_admin_ui_edit_form($form, $form_state, $workflow = NULL) {
-  $form = array();
-  // If we don't have a workflow by this point, we need to go back
-  // to creating one at admin/config/workflow/workflow/add
-  // I think the menu loader won't allow this to happen.
-  if (!$workflow) {
-    drupal_goto('admin/config/workflow/workflow/add');
-  }
-    // Create breadcrumbs.
-    workflow_admin_ui_breadcrumbs($workflow);
-
-    $noyes = array(t('No'), t('Yes'));
-
-    $form['wid'] = array('#type' => 'value', '#value' => $workflow->wid);
-    $form['#workflow'] = $workflow;
-
-    $form['basic'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Workflow information'),
-      );
-
-    $form['basic']['wf_name'] = array(
-      '#type' => 'textfield',
-      '#default_value' => $workflow->name,
-      '#title' => t('Workflow Name'),
-      '#maxlength' => '254',
-      '#required' => TRUE,
-      );
-
-    $form['basic']['name_as_title'] = array(
-      '#type' => 'radios',
-      '#options' => $noyes,
-      '#attributes' => array('class' => array('container-inline')),
-      '#title' => t('Use the workflow name as the title of the workflow form?'),
-      '#default_value' => isset($workflow->options['name_as_title']) ? $workflow->options['name_as_title'] : 0,
-      '#description' => t('The workflow section of the editing form is in its own fieldset. Checking the box will add the workflow ' .
-        'name as the title of workflow section of the editing form.'),
-      );
-
-    $form['comment'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Comment for Workflow Log'),
-      );
-
-    $form['comment']['comment_log_node'] = array(
-      '#type' => 'radios',
-      '#options' => $noyes,
-      '#attributes' => array('class' => array('container-inline')),
-      '#title' => t('Show a comment field in the workflow section of the editing form?'),
-      '#default_value' => isset($workflow->options['comment_log_node']) ? $workflow->options['comment_log_node'] : 0,
-      '#description' => t('On the node editing form, a Comment form can be shown so that the person making the state change can record ' .
-        'reasons for doing so. The comment is then included in the node\'s workflow history.'),
-      );
-
-    $form['comment']['comment_log_tab'] = array(
-      '#type' => 'radios',
-      '#options' => $noyes,
-      '#attributes' => array('class' => array('container-inline')),
-      '#title' => t('Show a comment field in the workflow section of the workflow tab form?'),
-      '#default_value' => isset($workflow->options['comment_log_tab']) ? $workflow->options['comment_log_tab'] : 0,
-      '#description' => t('On the workflow tab, a Comment form can be shown so that the person making the state change can record ' .
-        'reasons for doing so. The comment is then included in the node\'s workflow history.'),
-      );
-
-    $form['watchdog'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Watchdog Logging for Workflow'),
-      );
-
-    $form['watchdog']['watchdog_log'] = array(
-      '#type' => 'radios',
-      '#options' => $noyes,
-      '#attributes' => array('class' => array('container-inline')),
-      '#title' => t('Log informational watchdog messages when a transition is executed (state of a node is changed)?'),
-      '#default_value' => isset($workflow->options['watchdog_log']) ? $workflow->options['watchdog_log'] : 0,
-      '#description' => t('Optionally log transition state changes to watchdog.'),
-      );
-
-    $form['tab'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Workflow tab permissions'),
-      '#collapsible' => TRUE,
-      '#collapsed' => FALSE,
-      );
-
-    $form['tab']['tab_roles'] = array(
-      '#type' => 'checkboxes',
-      '#options' => workflow_admin_ui_get_roles(),
-      '#default_value' => explode(',', $workflow->tab_roles),
-      '#description' => t('Select any roles that should have access to the workflow tab on nodes that have a workflow.'),
-      );
-
-    $form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
-
-    return $form;
-}
-
-/**
- * Theme the workflow editing form.
- *
- * @see workflow_edit_form()
- */
-function theme_workflow_admin_ui_edit_form($variables) {
-  $output = '';
-
-  $form = $variables['form'];
-  $wid = $form['wid']['#value'];
-  $workflow = $form['#workflow'];
-
-  // If we don't have a workflow here, we need to go back to admin.
-  if ($workflow) {
-    drupal_set_title(t('Edit workflow %name', array('%name' => $workflow->name)), PASS_THROUGH);
-    $output .= drupal_render($form['wf_name']);
-
-    $output .= drupal_render_children($form);
-    return $output;
-  }
-  else {
-    drupal_goto('admin/config/workflow/workflow');
-  }
-}
-
-/**
- * Validate the workflow editing form.
- *
- * @see workflow_edit_form()
- */
-function workflow_admin_ui_edit_form_validate($form_id, $form_state) {
-  $wid = $form_state['values']['wid'];
-  // Make sure workflow name is not a duplicate.
-  if (array_key_exists('wf_name', $form_state['values']) /*&& $form_state['values']['wf_name'] != ''*/) {
-    $workflow_name = $form_state['values']['wf_name'];
-    $workflows = array();
-    foreach (workflow_get_workflows() as $data) {
-      $workflows[$data->wid] = check_plain(t($data->name));
-    }
-    $workflows = array_flip($workflows);
-    if (array_key_exists($workflow_name, $workflows) && $wid != $workflows[$workflow_name]) {
-      form_set_error('wf_name', t('A workflow with the name %name already exists. Please enter another name for this workflow.',
-        array('%name' => $workflow_name)));
-    }
-  }
-}
-
-/**
- * Submit handler for the workflow editing form.
- *
- * @see workflow_edit_form()
- */
-function workflow_admin_ui_edit_form_submit($form, &$form_state) {
-  if (isset($form_state['values']['transitions'])) {
-    workflow_update_transitions($form_state['values']['transitions']);
-  }
-
-  $options = array(
-    'comment_log_node' => $form_state['values']['comment_log_node'],
-    'comment_log_tab' => $form_state['values']['comment_log_tab'],
-    'name_as_title' => $form_state['values']['name_as_title'],
-    'watchdog_log' => $form_state['values']['watchdog_log'],
-    );
-
-  $workflow = array(
-    'wid' => $form_state['values']['wid'],
-    'name' => $form_state['values']['wf_name'],
-    'tab_roles' =>  array_filter($form_state['values']['tab_roles']),
-    'options' => serialize($options),
-    );
-
-  workflow_update_workflows($workflow);
-
-  drupal_set_message(t('The workflow was updated.'));
-
-  $form_state['redirect'] = 'admin/config/workflow/workflow/' . $form_state['values']['wid'];
-}
-
-/**
- * Menu callback. Edit a workflow's transitions.
- *
- * @param $wid
- *   The workflow object.
- * @return
- *   HTML form and permissions table.
- */
-function workflow_admin_ui_transitions_form($form, $form_state, $workflow) {
-  // Make sure we have a workflow.
   if ($workflow) {
-    // Create breadcrumbs.
-    workflow_admin_ui_breadcrumbs($workflow);
-
-    $form = array();
-    $form['wid'] = array('#type' => 'value', '#value' => $workflow->wid);
-    $form['#workflow'] = $workflow;
-
-    $form['transitions'] = workflow_admin_ui_transition_grid_form($workflow);
-
-    $form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
-
-    return $form;
-  }
-}
-
-/**
- * Validate the workflow editing form.
- *
- * @see workflow_edit_form()
- */
-function workflow_admin_ui_transitions_form_validate($form, $form_state) {
-  $wid = $form['wid']['#value'];
-  $workflow = $form['#workflow'];
-
-  // Make sure 'author' is checked for (creation) -> [something].
-  $creation_id = workflow_get_creation_state_by_wid($wid);
-  if (isset($form_state['values']['transitions'][$creation_id]) && is_array($form_state['values']['transitions'][$creation_id])) {
-    foreach ($form_state['values']['transitions'][$creation_id] as $to => $roles) {
-      if ($roles['author']) {
-        $author_has_permission = TRUE;
-        break;
-      }
-    }
-  }
-  $state_count = db_query('SELECT COUNT(sid) FROM {workflow_states} WHERE wid = :wid', array(':wid' => $wid))->fetchField();
-  if (empty($author_has_permission) && $state_count > 1) {
-    form_set_error('transitions', t('Please give the author permission to go from %creation to at least one state!',
-      array('%creation' => t('(creation)'))));
-  }
-}
-
-/**
- * Theme the workflow editing form.
- *
- * @see workflow_edit_form()
- */
-function theme_workflow_admin_ui_transitions_form($variables) {
-  $output = '';
-  $form = $variables['form'];
-
-  $wid = $form['wid']['#value'];
-  $workflow = $form['#workflow'];
-
-  // If we don't have a workflow here, we need to go back to admin.
-  if ($workflow) {
-    drupal_set_title(t('Edit workflow %name transitions', array('%name' => $workflow->name)), PASS_THROUGH);
-    $output .= drupal_render($form['wf_name']);
-
-    $states = workflow_get_workflow_states_by_wid($wid, array('status' => 1));
-    if ($states) {
-      $roles = workflow_admin_ui_get_roles();
-
-      $header = array(array('data' => t('From / To') . ' &nbsp;' . WORKFLOW_ARROW));
-      $rows = array();
-      foreach ($states as $state) {
-        $state_id = $state->sid;
-        $name = $state->state;
-        // Don't allow transition TO (creation).
-        if ($name != t('(creation)')) {
-          $header[] = array('data' => check_plain(t($name)));
-        }
-        $row = array(array('data' => check_plain(t($name))));
-        foreach ($states as $nested_state) {
-          $nested_state_id = $nested_state->sid;
-          $nested_name = $nested_state->state;
-          if ($nested_name == t('(creation)')) {
-            // Don't allow transition TO (creation).
-            continue;
-          }
-          if ($nested_state_id != $state_id) {
-            // Need to render checkboxes for transition from $state to $nested_state.
-            $from = $state_id;
-            $to = $nested_state_id;
-            $cell = '';
-            foreach ($roles as $rid => $role_name) {
-              $cell .= drupal_render($form['transitions'][$from][$to][$rid]);
-            }
-            $row[] = array('data' => $cell);
-          }
-          else {
-            $row[] = array('data' => '');
-          }
-        }
-        $rows[] = $row;
-      }
-      $output .= theme('table', array('header' => $header, 'rows' => $rows));
-    }
-    else {
-      $output = t('There are no states defined for this workflow.');
-    }
-
-    $output .= drupal_render_children($form);
-    return $output;
-  }
-}
-
-/**
- * Submit handler for the workflow editing form.
- *
- * @see workflow_edit_form()
- */
-function workflow_admin_ui_transitions_form_submit($form, &$form_state) {
-  $workflow = $form['#workflow'];
-
-  if (isset($form_state['values']['transitions'])) {
-    workflow_update_transitions($form_state['values']['transitions']);
+    $bc[] = l($workflow->label(), "admin/config/workflow/workflow/$workflow->wid");
   }
-
-  drupal_set_message(t('The workflow was updated.'));
-
-  $form_state['redirect'] = 'admin/config/workflow/workflow/' . $form_state['values']['wid'];
-}
-
-/**
- * Form builder. Build the grid of transitions for defining a workflow.
- *
- * @param int $wid
- *   The workflow object.
- */
-function workflow_admin_ui_transition_grid_form($workflow) {
-  $form = array('#tree' => TRUE);
-
-  $roles = workflow_admin_ui_get_roles();
-
-  $states = workflow_get_workflow_states_by_wid($workflow->wid, array('status' => 1));
-  if (!$states) {
-    $form['error'] = array(
-      '#type' => 'markup',
-      '#value' => t('There are no states defined for this workflow.'),
-    );
-    return $form;
-  }
-
-  foreach ($states as $state1) {
-    $state_id = $state1->sid;
-    $name = $state1->state;
-    foreach ($states as $state2) {
-      $nested_state_id = $state2->sid;
-      $nested_name = $state2->state;
-      if ($nested_name == t('(creation)')) {
-        // Don't allow transition TO (creation).
-        continue;
-      }
-      if ($nested_state_id != $state_id) {
-        // Need to generate checkboxes for transition from $state to $nested_state.
-        $from = $state_id;
-        $to = $nested_state_id;
-        foreach ($roles as $rid => $role_name) {
-          $checked = FALSE;
-          if ($transition = workflow_get_workflow_transitions_by_sid_target_sid($from, $to)) {
-            $checked = workflow_transition_allowed($transition->tid, $rid);
-          }
-          $form[$from][$to][$rid] = array(
-            '#type' => 'checkbox',
-            '#title' => check_plain($role_name),
-            '#default_value' => $checked,
-          );
-        }
-      }
-    }
-  }
-  return $form;
-}
-
-/**
- * Implements hook_workflow_operations().
- * Might as well eat our own cooking.
- */
-function workflow_admin_ui_workflow_operations($op, $workflow = NULL, $state = NULL) {
-  switch ($op) {
-    case 'top_actions':
-      // Build a link to each workflow.
-      $workflows = workflow_get_workflows();
-      $alt = t('Add a new workflow');
-      $actions = array(
-        'add-workflow' => array(
-          'title' => t('Add workflow'),
-          // @TODO: It might be more sane to go to the "settings" page.
-          'href' => 'admin/config/workflow/workflow/add',
-          'attributes' => array('alt' => $alt, 'title' => $alt),
-          ),
-        );
-
-      foreach ($workflows as $workflow) {
-        $alt = t('Work with @wf', array('@wf' => $workflow->name));
-        $actions[drupal_html_class($workflow->name)] = array(
-          'title' => t($workflow->name),
-          'href' => "admin/config/workflow/workflow/$workflow->wid",
-          'attributes' => array('alt' => $alt, 'title' => $alt),
-          );
-      }
-
-      return $actions;
-
-    case 'workflow':
-      $actions = array(
-        'workflow_settings' => array(
-          'title' => t('Settings'),
-          'href' => "admin/config/workflow/workflow/edit/$workflow->wid",
-          'attributes' => array('alt' => t('Edit the @wf settings', array('@wf' => $workflow->name))),
-          ),
-
-        'workflow_transitions' => array(
-          'title' => t('Transitions'),
-          'href' => "admin/config/workflow/workflow/transitions/$workflow->wid",
-          'attributes' => array('alt' => t('Edit the @wf transitios', array('@wf' => $workflow->name))),
-          ),
-
-        'workflow_permission_summary' => array(
-          'title' => t('Summary'),
-          'href' => "admin/config/workflow/workflow/perm_summary/$workflow->wid",
-          'attributes' => array('alt' => t('See a summary of the @wf transitios', array('@wf' => $workflow->name))),
-          ),
-
-        'workflow_overview_delete' => array(
-          'title' => t('Delete'),
-          'href' => "admin/config/workflow/workflow/delete/$workflow->wid",
-          'attributes' => array('alt' => t('Delete the @wf', array('@wf' => $workflow->name))),
-          ),
-        );
-
-      foreach ($actions as $name => $link) {
-        $actions[$name]['attributes']['title'] = $actions[$name]['attributes']['alt'];
-      }
-
-      return $actions;
+  if ($extra) {
+    $bc[] = $extra;
   }
-}
-
-/**
- * Menu callback. Create the main workflow page, which gives an overview
- * of workflows and workflow states.
- * Replaced by http://drupal.org/node/1367530.
- */
-function workflow_admin_ui_overview_form($form, $form_state, $workflow) {
-  $bc = array(l(t('Home'), '<front>'));
-  $bc[] = l(t('Configuration'), 'admin/config');
-  $bc[] = l(t('Workflow'), 'admin/config/workflow');
-  $bc[] = l(t('Workflow'), 'admin/config/workflow/workflow');
   drupal_set_breadcrumb($bc);
-
-  $form = array('#tree' => TRUE);
-
-  $form['wid'] = array('#type' => 'value', '#value' => $workflow->wid);
-  $form['#wf_name'] = $workflow->name;
-
-  // Get the state objects and make the array keys the same as the sids.
-  $states = workflow_get_workflow_states_by_wid($workflow->wid);
-  $sids = array();
-  foreach ($states as $state) {
-    $sids[] = $state->sid;
-  }
-  $states = array_combine($sids, $states);
-
-  // Save the list in the form for later.
-  $form['#workflow_states'] = $states;
-
-  // Allow modules to insert their own workflow operations.
-  $links = module_invoke_all('workflow_operations', 'workflow', $workflow);
-
-  drupal_set_title(t('Workflow: ') . t($workflow->name));
-
-  $links_args = array(
-    'links' => $links,
-    'attributes' => array('class' => array('inline', 'action-links')),
-    );
-
-  $form['action-links'] = array(
-    '#type' => 'markup',
-    '#markup' => theme('links', $links_args),
-    );
-
-  // Build select options for reassigning states.
-  // We put a blank state first for validation.
-  $state_list = array('' => ' ');
-  foreach ($states as $sid => $state) {
-    // Leave out the creation state and any inactive states.
-    if ($state->sysid == 0 && $state->status == 1) {
-      $state_list[$sid] = $state->state;
-    }
-  }
-  // Is this the last state available?
-  $form['#last'] = count($state_list) == 2;
-
-  // Dummy object for new state item.
-  $states[] = (object) array(
-    'sid' => 0,
-    'state' => '',
-    'status' => 1,
-    'sysid' => 0,
-    'weight' => 99,
-    );
-
-  foreach ($states as $state) {
-    // Allow modules to insert state operations.
-    $state_links = module_invoke_all('workflow_operations', 'state', $workflow, $state);
-
-    $form['states'][$state->sid] = array(
-      'state' => array(
-        '#type' => 'textfield',
-        '#size' => 30,
-        '#maxlength' => 255,
-        '#default_value' => $state->state,
-        ),
-
-      'weight' => array(
-        '#type' => 'weight',
-        '#delta' => 20,
-        '#default_value' => $state->weight,
-        '#title-display' => 'invisible',
-        '#attributes' => array('class' => array('state-weight')),
-        ),
-
-      'status' => array(
-        '#type' => 'checkbox',
-        '#default_value' => $state->status,
-        ),
-
-      // Save the original status for the validation handler.
-      'orig_status' => array(
-        '#type' => 'value',
-        '#value' => $state->status,
-        ),
-
-      'reassign' => array(
-        '#type' => 'select',
-        '#options' => $state_list,
-        ),
-
-      'count' => array(
-        '#type' => 'value',
-        '#value' => count(workflow_get_workflow_node_by_sid($state->sid)),
-        ),
-
-      'ops' => array(
-        '#type' => 'markup',
-        '#markup' => theme('links', array('links' => $state_links)),
-        ),
-
-      'sysid' => array(
-        '#type' => 'value',
-        '#value' => $state->sysid,
-        ),
-      );
-
-    // Don't let the creation state change weight or status or name.
-    if ($state->sid == $workflow->creation_state) {
-      $form['states'][$state->sid]['weight']['#value'] = -50;
-      $form['states'][$state->sid]['sysid']['#value'] = 1;
-      $form['states'][$state->sid]['state']['#disabled'] = TRUE;
-      $form['states'][$state->sid]['status']['#disabled'] = TRUE;
-      $form['states'][$state->sid]['reassign']['#disabled'] = TRUE;
-     }
-  }
-
-  $form['instructions'] = array(
-    '#type' => 'markup',
-    '#markup' => '<p>' . t('You may enter a new state name in the empty space.
-      Check the "Active" box to make it effective. You may also drag it to the appropriate position.') . '</p>'
-      . '<p>' . t('A state not marked as active will not be shown as available in the workflow settings.') . '</p>'
-      . '<p>' . t('If you wish to inactivate a state that has content (i.e. count is not zero),
-        then you need to select a state to which to reassign that content.') . '</p>'
-    );
-
-
-  $form['actions'] = array('#type' => 'actions');
-  $form['actions']['submit'] = array( '#type' => 'submit', '#value' => t('Save Changes'));
-
-  return $form;
-}
-
-function theme_workflow_admin_ui_overview_form($variables) {
-  $form = $variables['form'];
-  $output = '';
-  $table_id = 'workflow_admin_ui_overview';
-
-  $table = array(
-    'rows' => array(),
-    'header' => array(
-      t('State'),
-      t('Weight'),
-      t('Active'),
-      t('Reassign'),
-      t('Count'),
-      array('data' => t('Operations'), 'class' => 'state-ops'),
-      ),
-    'attributes' => array('id' => $table_id, 'style' => 'width: auto;'),
-    );
-
-  // The out put needs to have the action links at the top.
-  $output .= drupal_render($form['action-links']);
-
-  // Iterate over each element in our $form['states'] array
-  foreach (element_children($form['states']) as $id) {
-    // We are now ready to add each element of our $form data to the rows
-    // array, so that they end up as individual table cells when rendered
-    // in the final table.  We run each element through the drupal_render()
-    // function to generate the final html markup for that element.
-    $table['rows'][] = array(
-      'data' => array(
-        // Add our 'name' column
-        array('data' => drupal_render($form['states'][$id]['state']), 'class' => 'state-name'),
-        // Add our 'weight' column
-        drupal_render($form['states'][$id]['weight']),
-        // Add our 'status' column
-        array('data' => drupal_render($form['states'][$id]['status']), 'class' => 'state-status'),
-        // Add our 'reassign' column
-        array('data' => drupal_render($form['states'][$id]['reassign']), 'class' => 'state-reassign'),
-        // Add our 'count' column
-        array('data' => $form['states'][$id]['count']['#value'], 'class' => 'state-count'),
-        // Add our 'operations' column
-        array('data' => drupal_render($form['states'][$id]['ops']), 'class' => 'state-ops'),
-        // Add our 'sysid' column
-        drupal_render($form['states'][$id]['sysid']),
-      ),
-      // To support the tabledrag behaviour, we need to assign each row of the
-      // table a class attribute of 'draggable'. This will add the 'draggable'
-      // class to the <tr> element for that row when the final table is
-      // rendered.
-      'class' => array('draggable'),
-      );
-  }
-
-  $output .= theme('table', $table);
-
-  // And then render any remaining form elements (such as our submit button)
-  $output .= drupal_render_children($form);
-
-  // We now call the drupal_add_tabledrag() function in order to add the
-  // tabledrag.js goodness onto our page.
-  //
-  // For a basic sortable table, we need to pass it:
-  //   - the $table_id of our <table> element,
-  //   - the $action to be performed on our form items ('order'),
-  //   - a string describing where $action should be applied ('siblings'),
-  //   - and the class of the element containing our 'weight' element.
-  drupal_add_tabledrag($table_id, 'order', 'sibling', 'state-weight');
-
-  return $output;
-}
-
-/**
- * Validation handler for the state form.
- */
-function workflow_admin_ui_overview_form_validate($form, &$form_state) {
-  // Get the workflow id.
-  $wid = $form_state['values']['wid'];
-
-  // Get the state objects.
-  $states = $form['#workflow_states'];
-
-  // Because the form elements were keyed with the item ids from the database,
-  // we can simply iterate through the submitted values.
-  foreach ($form_state['values']['states'] as $sid => $item) {
-    // Do they want to deactivate the state?
-    if (($item['status'] != $item['orig_status']) && ($item['status'] == 0)) {
-      // Does that state have nodes in it?
-      if ($item['count'] > 0 && empty($item['reassign'])) {
-        if ($form['#last']) {
-          drupal_set_message(t('Since you are deleting the last available workflow state
-          in this workflow, all content items which are in that state will have their
-          workflow state removed.'), 'warning');
-        }
-        else {
-        form_set_error("states'][$sid]['reassign'",
-          t('The %state state has content; you must reassign the content to another state.',
-            array('%state' => $states[$sid]->state)));
-        }
-      }
-    }
-  }
-}
-
-/**
- * Submission handler for the state form.
- */
-function workflow_admin_ui_overview_form_submit($form, &$form_state) {
-  // Get the workflow id, then save it for the next round.
-  $wid = $form_state['values']['wid'];
-  $wf_name = $form['#wf_name'];
-
-  // Get the state objects.
-  $states = $form['#workflow_states'];
-
-  // Because the form elements were keyed with the item ids from the database,
-  // we can simply iterate through the submitted values.
-  foreach ($form_state['values']['states'] as $id => $item) {
-    $item['sid'] = $id;
-    $item['wid'] = $wid;
-
-    // Is there not a new state name?
-    if (empty($item['state'])) {
-      // No new state entered, so skip it.
-      continue;
-    }
-
-    // Is this a new state?
-    if ($id == 0) {
-      // Remove the key for the updating.
-      unset($item['sid']);
-    }
-
-    // Do they want to deactivate the state?
-    if ($item['status'] == 0) {
-      // Does that state have nodes in it?
-      if ($item['count'] > 0) {
-        if ($form['#last']) {
-          $new_sid = NULL;
-          drupal_set_message(t('Removing workflow states from content in the %workflow.',
-            array('%workflow' => $wf_name)));
-        }
-        else {
-          // Prepare the state delete function.
-          $new_sid = $item['reassign'];
-          drupal_set_message(t('Reassigning content from %old_state to %new_state.',
-            array('%old_state' => $states[$id]->state, '%new_state' => $states[$new_sid]->state)));
-        }
-
-        // Delete the old state without orphaning nodes, move them to the new state.
-        workflow_delete_workflow_states_by_sid($id, $new_sid);
-
-        $args = array(
-          '%state' => $states[$id]->state,
-          '%workflow' => $wf_name,
-          );
-        watchdog('workflow', 'Deactivated workflow state %state in %workflow.', $args);
-        drupal_set_message(t('The workflow state %state was deleted from %workflow.', $args));
-      }
-    }
-
-    // Call the update function.
-    workflow_update_workflow_states($item);
-  }
-
-  $form_state['redirect'] = "admin/config/workflow/workflow/$wid";
-}
-
-/**
- * Form builder. Allow administrator to map workflows to content types
- * and determine placement.
- */
-function workflow_admin_ui_types_form($form) {
-  $form = array();
-
-  // Allow modules to insert their own action links.
-  $links = module_invoke_all('workflow_operations', 'top_actions', NULL);
-  $links_args = array(
-    'links' => $links,
-    'attributes' => array('class' => array('inline', 'action-links')),
-    );
-  $form['action-links'] = array(
-    '#type' => 'markup',
-    '#markup' => theme('links', $links_args),
-    );
-
-  $these_workflows = array();
-  foreach (workflow_get_workflows() as $data) {
-    // Don't allow workflows with no states.
-    $states = workflow_get_workflow_states_by_wid($data->wid, array('status' => 1));
-
-    // There should always be a creation state.
-    if (count($states) == 1) {
-      // That's all, so let's remind them to create some states.
-      drupal_set_message(t('%workflow has no states defined, so it cannot be assigned to content yet.',
-        array('%workflow' => ucwords($data->name))), 'warning');
-
-      // Skip allowing this workflow.
-      continue;
-    }
-
-    // Also check for transitions at least out of the creation state.
-    // This always gets at least the "from" state.
-    $transitions = workflow_allowable_transitions($data->creation_state, 'to');
-    if (count($transitions) == 1) {
-      // That's all, so let's remind them to create some transitions.
-      drupal_set_message(t('%workflow has no transitions defined, so it cannot be assigned to content yet.',
-        array('%workflow' => ucwords($data->name))), 'warning');
-
-      // Skip allowing this workflow.
-      continue;
-    }
-
-    // Add this workflow to the allowed list.
-    $these_workflows[$data->wid] = t($data->name);
-  }
-
-  $workflows = array(t('None')) + $these_workflows;
-  if (count($workflows) == 1) {
-    drupal_set_message(t('You must create at least one workflow before content can be assigned to a workflow.'));
-    return $form;
-  }
-
-  $type_map = workflow_get_workflow_type_map();
-
-  $form['#tree'] = TRUE;
-  $form['help'] = array(
-    '#type' => 'item',
-    '#title' => '<h3>' . t('Content Type Mapping') . '</h3>',
-    '#markup' => t('Each content type may have a separate workflow. The form for changing workflow state can be '
-      . 'displayed when editing a node, editing a comment for a node, or both.'),
-    );
-
-  $placement_options = array(
-        'node' => t('Post'),
-        'comment' => t('Comment'),
-        );
-
-  foreach (node_type_get_names() as $type => $name) {
-    $form[$type]['workflow'] = array(
-      '#type' => 'select',
-      '#options' => $workflows,
-      '#default_value' => isset($type_map[$type]) ? $type_map[$type] : 0,
-      );
-
-    $form[$type]['placement'] = array(
-      '#type' => 'checkboxes',
-      '#options' => $placement_options,
-      '#default_value' => variable_get('workflow_' . $type, array()),
-      );
-  }
-
-  $form['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Save workflow mapping'),
-    '#weight' => 100,
-    );
-
-  return $form;
-}
-
-/**
- * Theme the workflow type mapping form.
- */
-function theme_workflow_admin_ui_types_form($variables) {
-  $output = '';
-  $form = $variables['form'];
-
-  // Do the action links at the top.
-  $output .= drupal_render($form['action-links']);
-
-  $header = array(t('Content Type'), t('Workflow'), t('Display Workflow Form on:'));
-  $rows = array();
-
-  foreach (node_type_get_names() as $type => $name) {
-    $rows[] = array(
-      check_plain(t($name)),
-      drupal_render($form[$type]['workflow']),
-      drupal_render($form[$type]['placement']),
-      );
-  }
-
-  $output .= drupal_render($form['help']);
-  $output .= theme('table', array(
-    'header' => $header,
-    'rows' => $rows,
-    'attributes' => array('style' => 'width: auto; clear: both;'),
-    ));
-
-  return $output . drupal_render_children($form);
-}
-
-/**
- * Submit handler for workflow type mapping form.
- *
- * @see workflow_types_form()
- */
-function workflow_admin_ui_types_form_submit($form, &$form_state) {
-  workflow_admin_ui_types_save($form_state['values']);
-
-  drupal_set_message(t('The workflow mapping was saved.'));
-}
-
-/**
- * Get a list of roles.
- *
- * @return
- *   Array of role names keyed by role ID, including the 'author' role.
- */
-function workflow_admin_ui_get_roles() {
-  static $roles = NULL;
-  if (!$roles) {
-    $roles = array('author' => 'author');
-    $list = user_roles(FALSE, 'participate in workflow');
-    foreach ($list as $rid => $name) {
-      $roles[$rid] = check_plain($name);
-    }
-  }
-  return $roles;
-}
-
-/**
- * Save mapping of workflow to node type. E.g., the story node type is using the Foo workflow.
- *
- * @param $form_state['values']
- */
-function workflow_admin_ui_types_save($form_values) {
-  // Empty the table so that types no longer under workflow go away.
-  workflow_delete_workflow_type_map_all();
-
-  $node_types = node_type_get_names();
-  foreach ($node_types as $type => $name) {
-    $data = array(
-      'type' => $type,
-      'wid' => $form_values[$type]['workflow'],
-    );
-    workflow_insert_workflow_type_map($data);
-    variable_set('workflow_' . $type, array_keys(array_filter(($form_values[$type]['placement']))));
-
-    // If this type uses workflow, make sure pre-existing nodes are set
-    // to the workflow's creation state.
-    if ($form_values[$type]['workflow']) {
-      workflow_initialize_nodes($type, $name);
-    }
-  }
-}
-
-/**
- * Initialize all pre-existing nodes of a type to their first state..
- *
- * @param $type - node type
- * @param $name - node type name
- */
-function workflow_initialize_nodes($type, $name) {
-  // Build the select query.
-  // We want all published nodes of this type that don't already have a workflow state.
-  $query = db_select('node', 'n');
-  $query->leftJoin('workflow_node', 'wn', 'wn.nid = n.nid');
-  // Add the fields.
-  $query->addField('n', 'nid');
-  // Add conditions.
-  $query->condition('n.type', $type);
-  $query->condition('n.status', 1);
-  $query->isNull('wn.sid');
-
-  $nids = $query->execute()->fetchCol();
-  $how_many = count($nids);
-  if ($how_many == 0) {
-    return;
-  }
-  $comment = t('Pre-existing content set to initial state.');
-
-  // Get the initial state for this content type.
-  $first_state = db_query("SELECT s.sid "
-    . "FROM {workflow_type_map} m "
-    . "INNER JOIN {workflow_states} s ON s.wid = m.wid "
-    . "WHERE m.type = :type AND s.sysid <> :creation "
-    . "ORDER BY s.weight ASC ",
-    array(':type' => $type, ':creation' => WORKFLOW_CREATION)
-    )->fetchField(0);
-
-  // Load them all up.
-  $nodes = node_load_multiple($nids);
-  foreach ($nodes as $node) {
-    // Force it to transition to the first state and get a history record.
-    workflow_execute_transition($node, $first_state, $comment, TRUE);
-  }
-  return;
-
-  drupal_set_message(t('!count @type nodes have been initialized.', array('@type' => $name, '!count' => $how_many)));
-}
-
-/**
- * Update the transitions for a workflow.
- *
- * @param array $transitions from values.
- *   Transitions, for example:
- *     18 => array(
- *       20 => array(
- *         'author' => 1,
- *         1        => 0,
- *         2        => 1,
- *       )
- *     )
- *   means the transition from state 18 to state 20 can be executed by
- *   the node author or a user in role 2. The $transitions array should
- *   contain ALL transitions for the workflow.
- */
-function workflow_update_transitions($transitions = array()) {
-  // Empty string is sometimes passed in instead of an array.
-  if (!$transitions) {
-    return;
-  }
-  foreach ($transitions as $from => $to_data) {
-    foreach ($to_data as $to => $role_data) {
-      foreach ($role_data as $role => $can_do) {
-        if ($can_do) {
-          $transition = array(
-            'sid' => $from,
-            'target_sid' => $to,
-            'roles' => $role,
-          );
-          workflow_update_workflow_transitions($transition);
-        }
-        else {
-          $roles = array();
-          if ($transition = workflow_get_workflow_transitions_by_sid_target_sid($from, $to)) {
-            $roles = explode(',', $transition->roles);
-            $tid = $transition->tid;
-            if (($i = array_search($role, $roles)) !== FALSE) {
-              unset($roles[$i]);
-              workflow_update_workflow_transitions_roles($tid, $roles);
-            }
-          }
-        }
-      }
-    }
-  }
-  workflow_delete_workflow_transitions_by_roles('');
 }

+ 100 - 0
sites/all/modules/contrib/admin/workflow/workflow_admin_ui/workflow_admin_ui.page.labels.inc

@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * @file
+ * Provides an Admin UI page for the Workflow Transition labels.
+ *
+ * Modify the workflow form items so specific workflow transitions
+ * can have their own labels which the admin can describe relative
+ * to the beginning and ending states.
+ * Rather than showing the user a workflow box containing options like
+ * "review required" as a state in the workflow, it could say "move to
+ * the editing department for grammar review".
+ *
+ * AUTHOR
+ * ------
+ * David Kent Norman (http://deekayen.net/)
+ *
+ * Amazon Honor System donation:
+ * http://zme.amazon.com/exec/varzea/pay/T2EOCSRRDQ9CL2
+ *
+ * Paypal donation:
+ * https://www.paypal.com/us/cgi-bin/webscr?cmd=_xclick&business=paypal@deekayen.net&item_name=Drupal%20contribution&currency_code=USD&amount=20.00
+ */
+
+/**
+ * Label edit form, where each fieldset represents a starting workflow state.
+ *
+ * Each contains the transitions with that starting workflow state.
+ *
+ * @return array
+ *   Array of form items for editing labels on transitions.
+ */
+function workflow_admin_ui_labels_form($form, $form_state, $workflow, $op) {
+  if (!is_object($workflow)) {
+    drupal_set_message(t('Improper worklow ID provided.'), 'error');
+    watchdog('workflow_named_transitions', 'Improper worklow ID provided.');
+    drupal_goto('admin/config/workflow/workflow');
+  }
+
+  $headers = array(
+    t('Transition from'),
+    t('to'),
+    t('label'),
+  );
+
+  $rows = array();
+  $previous_from_sid = -1;
+  // Get transitions, sorted by weight of the old state.
+  $config_transitions = $workflow->getTransitions();
+  foreach ($config_transitions as $transition) {
+    $old_state = $transition->getOldState();
+    $new_state = $transition->getNewState();
+    $rows[] = array(
+      'data' => array(
+        array('data' => (($previous_from_sid != $transition->sid) ? $old_state->label() : '"')),
+        array('data' => $new_state->label()),
+        array(
+          'data' => array(
+            '#type' => 'textfield',
+            '#value' => $transition->label(),
+            '#size' => 60,
+            '#maxlength' => 128,
+            '#name' => 'label_' . $transition->tid,
+            '#id' => 'label_' . $transition->tid,
+          ),
+        ),
+      ),
+    );
+    $previous_from_sid = $transition->sid;
+  }
+
+  $form['transition_labels'] = array(
+    '#theme' => 'table',
+    '#header' => $headers,
+    '#rows' => $rows,
+  );
+
+  // Save the transitions in the form to fetch upon submit.
+  $form['#transitions'] = $config_transitions;
+
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => 'Submit',
+  );
+
+  return $form;
+}
+
+/**
+ * Automatic submission handler for the Transition labels form.
+ *
+ * @see workflow_admin_ui_labels_form()
+ */
+function workflow_admin_ui_labels_form_submit($form, &$form_state) {
+  foreach ($form['#transitions'] as $config_transition) {
+    $config_transition->label = trim($form_state['input']['label_' . $config_transition->tid]);
+    $config_transition->save();
+  }
+  drupal_set_message(t('The transition labels have been saved.'));
+}

+ 54 - 0
sites/all/modules/contrib/admin/workflow/workflow_admin_ui/workflow_admin_ui.page.permissions.inc

@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @file
+ * Provides an Admin UI page for the Workflow Permissions.
+ */
+
+/**
+ * View workflow permissions by role.
+ *
+ * @param object $workflow
+ *   The workflow object.
+ * @param $op (optional)
+ *
+ * @return string
+ */
+function workflow_admin_ui_view_permissions_form($workflow, $op) {
+  // If we don't have a workflow at this point, go back to admin page.
+  if (!$workflow) {
+    drupal_goto('admin/config/workflow/workflow');
+  }
+
+  $all = array();
+  $roles = workflow_get_roles();
+  foreach ($roles as $rid => $value) {
+    $all[$rid]['name'] = $value;
+  }
+
+  $transitions = $workflow->getTransitions();
+  foreach ($transitions as $transition) {
+    foreach ($transition->roles as $rid) {
+      $old_state = $transition->getOldState();
+      $new_state = $transition->getNewState();
+      $all[$rid]['transitions'][] = array(check_plain(t($old_state->label())), WORKFLOW_ADMIN_UI_ARROW, check_plain(t($new_state->label())));
+    }
+  }
+
+  $header = array(t('From'), '', t('To'));
+  $output = '';
+
+  // @todo: we should theme out the html here.
+  foreach ($all as $rid => $value) {
+    $role_name = !empty($value['name']) ? $value['name'] : t('deleted role !rid', array('!rid' => $rid));
+    $output .= '<h3>' . t('%role may do these transitions:', array('%role' => $role_name)) . '</h3>';
+    if (!empty($value['transitions'])) {
+      $output .= theme('table', array('header' => $header, 'rows' => $value['transitions'])) . '<p></p>';
+    }
+    else {
+      $output .= '<table><tbody><tr class="odd"><td>' . t('None') . '</td><td></tr></tbody></table><p></p>';
+    }
+  }
+
+  return $output;
+}

+ 328 - 0
sites/all/modules/contrib/admin/workflow/workflow_admin_ui/workflow_admin_ui.page.states.inc

@@ -0,0 +1,328 @@
+<?php
+
+/**
+ * @file
+ * Provides an Admin UI page for the Workflow States.
+ */
+
+/**
+ * Menu callback.
+ *
+ * Creates the main workflow page, which gives an overview
+ * of workflows and workflow states.
+ * Replaced by http://drupal.org/node/1367530.
+ */
+function workflow_admin_ui_states_form($form, &$form_state, $workflow, $op) {
+  $form = array();
+  $form['#tree'] = TRUE;
+  $form['workflow'] = array(
+    '#type' => 'value',
+    '#value' => $workflow,
+  );
+
+  // Build select options for reassigning states.
+  // We put a blank state first for validation.
+  $state_list = array('' => ' ');
+  $state_list += workflow_get_workflow_state_names($workflow->wid, $grouped = FALSE, $all = FALSE);
+  // Is this the last state available?
+  $form['#last_mohican'] = count($state_list) == 2;
+
+  // Get the state objects as array ($sid => WorkflowState).
+  $states = $workflow->getStates($all = TRUE);
+  // Create a dummy object for new state item. It must NOT be saved to DB.
+  $maxweight = $minweight = -50;
+  // $wid = $workflow->wid;
+  $dummy = $workflow->createState('', FALSE);
+  $dummy->weight = $minweight;
+  $states[$dummy->sid] = $dummy; // Although the index is 0, the state is appended at the end of the list.
+
+  foreach ($states as $state) {
+    // Allow modules to insert operations per state.
+    $links = module_invoke_all('workflow_operations', 'state', $workflow, $state);
+
+    $sid = $state->sid;
+    $label = $state->label();
+    $count = $state->count();
+
+    // Make it impossible to reassign to the same state that is disabled.
+    if ($state->isCreationState() || !$sid || !$state->isActive()) {
+      $current_state = array();
+      $current_state_list = array();
+    }
+    else {
+      $current_state = array($sid => $state_list[$sid]);
+      $current_state_list = array_diff($state_list, $current_state);
+    }
+
+    $form['states'][$sid] = array(
+      'state' => array(
+        '#type' => 'textfield',
+        '#size' => 30,
+        '#maxlength' => 255,
+        '#default_value' => check_plain($label),
+      ),
+
+      'name' => array(
+        '#type' => 'machine_name',
+        '#size' => 30,
+        '#maxlength' => 255,
+        '#required' => FALSE,
+        // '#disabled' => !empty($name), // If needed this would disable updating machine name, once set.
+        '#default_value' => $state->getName(),
+        '#machine_name' => array(
+          'exists' => 'workflow_admin_ui_states_validate_state_machine_name',
+          'source' => array('states', $sid, 'state'),
+          'replace_pattern' => '[^a-z0-9_()]+', // Added '()' characters from exclusion list since creation state has it.
+        ),
+      ),
+
+      'weight' => array(
+        '#type' => 'weight',
+        '#delta' => 20,
+        '#default_value' => $state->weight,
+        '#title-display' => 'invisible',
+        '#attributes' => array('class' => array('state-weight')),
+      ),
+
+      'status' => array(
+        '#type' => 'checkbox',
+        '#default_value' => $state->isActive(),
+      ),
+
+      // Save the original status for the validation handler.
+      'orig_status' => array(
+        '#type' => 'value',
+        '#value' => $state->isActive(),
+      ),
+
+      'reassign' => array(
+        '#type' => 'select',
+        '#options' => $current_state_list,
+      ),
+
+      'count' => array(
+        '#type' => 'value',
+        '#value' => $count,
+      ),
+
+      'ops' => array(
+        '#type' => 'markup',
+        '#markup' => theme('links', array('links' => $links)),
+      ),
+    );
+
+    // Don't let the creation state change weight or status or name.
+    if ($state->isCreationState()) {
+      $form['states'][$sid]['weight']['#value'] = $minweight;
+      $form['states'][$sid]['sysid']['#value'] = 1;
+      $form['states'][$sid]['state']['#disabled'] = TRUE;
+      $form['states'][$sid]['name']['#disabled'] = TRUE;
+      $form['states'][$sid]['status']['#disabled'] = TRUE;
+      $form['states'][$sid]['reassign']['#type'] = 'hidden';
+      $form['states'][$sid]['reassign']['#disabled'] = TRUE;
+    }
+    // New state and disabled states cannot be reassigned.
+    if (!$sid || !$state->isActive() || ($count == 0) ) {
+      $form['states'][$sid]['reassign']['#type'] = 'hidden';
+      $form['states'][$sid]['reassign']['#disabled'] = TRUE;
+    }
+    // Disabled states cannot be renamed (and is a visual clue, too.).
+    if (!$state->isActive()) {
+      $form['states'][$sid]['state']['#disabled'] = TRUE;
+    }
+    // Set a proper weight to the new state.
+    $maxweight = max($maxweight, $state->weight);
+    if (!$sid) {
+      $form['states'][$sid]['weight']['#default_value'] = $maxweight + 1;
+    }
+  }
+
+  $form['actions'] = array('#type' => 'actions');
+  $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save Changes'));
+
+  return $form;
+}
+
+function theme_workflow_admin_ui_states_form($variables) {
+  $form = $variables['form'];
+  $output = '';
+  $table_id = 'workflow_admin_ui_states';
+
+  $table = array(
+    'rows' => array(),
+    'header' => array(
+      t('State'),
+      t('Weight'),
+      t('Active'),
+      t('Reassign'),
+      t('Count'),
+      array('data' => t('Operations'), 'class' => 'state-ops'),
+    ),
+    'attributes' => array('id' => $table_id, 'style' => 'width: auto;'),
+  );
+
+  // The output needs to have the action links at the top.
+  $output .= drupal_render($form['action-links']);
+
+  // Iterate over each element in our $form['states'] array.
+  foreach (element_children($form['states']) as $id) {
+    // We are now ready to add each element of our $form data to the rows
+    // array, so that they end up as individual table cells when rendered
+    // in the final table.  We run each element through the drupal_render()
+    // function to generate the final html markup for that element.
+    $table['rows'][] = array(
+      'data' => array(
+        // Add our 'name' column.
+        // array('data' => drupal_render($form['states'][$id]['state']), 'class' => 'state-name'),
+        array('data' => drupal_render($form['states'][$id]['state']) . drupal_render($form['states'][$id]['name']), 'class' => 'state-name'),
+        // Add our 'weight' column.
+        drupal_render($form['states'][$id]['weight']),
+        // Add our 'status' column.
+        array('data' => drupal_render($form['states'][$id]['status']), 'class' => 'state-status'),
+        // Add our 'reassign' column.
+        array('data' => drupal_render($form['states'][$id]['reassign']), 'class' => 'state-reassign'),
+        // Add our 'count' column.
+        array('data' => $form['states'][$id]['count']['#value'], 'class' => 'state-count'),
+        // Add our 'operations' column.
+        array('data' => drupal_render($form['states'][$id]['ops']), 'class' => 'state-ops'),
+      ),
+      // To support the tabledrag behavior, we need to assign each row of the
+      // table a class attribute of 'draggable'. This will add the 'draggable'
+      // class to the <tr> element for that row when the final table is
+      // rendered.
+      'class' => array('draggable'),
+    );
+  }
+
+  $output .= theme('table', $table);
+
+  // And then render any remaining form elements (such as our submit button).
+  $output .= drupal_render_children($form);
+
+  // We now call the drupal_add_tabledrag() function in order to add the
+  // tabledrag.js goodness onto our page.
+  //
+  // For a basic sortable table, we need to pass it:
+  // - the $table_id of our <table> element,
+  // - the $action to be performed on our form items ('order'),
+  // - a string describing where $action should be applied ('siblings'),
+  // - and the class of the element containing our 'weight' element.
+  drupal_add_tabledrag($table_id, 'order', 'sibling', 'state-weight');
+
+  return $output;
+}
+
+/**
+ * Validation handler for the state form.
+ */
+function workflow_admin_ui_states_form_validate($form, &$form_state) {
+  // Because the form elements were keyed with the item ids from the database,
+  // we can simply iterate through the submitted values.
+  foreach ($form_state['values']['states'] as $sid => $item) {
+    // Reload $state from db, in case the states were changed by anyone else. And it is just as fast.
+    $state = workflow_state_load_single($sid);
+
+    // Does user want to deactivate the state (reassign current nodes)?
+    if ($sid > 0 && $item['status'] == 0 && $state->isActive()) {
+      $args = array('%state' => $state->getName()); // check_plain() is run by t().
+
+      // Does that state have nodes in it?
+      if ($item['count'] > 0 && empty($item['reassign'])) {
+        if ($form['#last_mohican']) {
+          $message = 'Since you are deleting the last available workflow state
+            in this workflow, all content items which are in that state will have their
+            workflow state removed.';
+          drupal_set_message(t($message, $args), 'warning');
+        }
+        else {
+          $message = 'The %state state has content; you must reassign the content to another state.';
+          form_set_error("states'][$sid]['reassign'", t($message, $args));
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Submission handler for the state form.
+ */
+function workflow_admin_ui_states_form_submit($form, &$form_state) {
+  // Get the workflow id, then save it for the next round.
+  $workflow = $form_state['values']['workflow'];
+  $wid = $workflow->wid;
+
+  // Because the form elements were keyed with the item ids from the database,
+  // we can simply iterate through the submitted values.
+  foreach ($form_state['values']['states'] as $sid => $item) {
+    $item['sid'] = $sid;
+    $item['wid'] = $wid;
+
+    // Is there not a new state name?
+    if (empty($item['state'])) {
+      // No new state entered, so skip it.
+      continue;
+    }
+
+    // Reload $state from db, in case the states were changed by anyone else. And it is just as fast.
+    $state = ($sid) ? workflow_state_load_single($sid) : $workflow->createState('');
+
+    // Does user want to deactivate the state (reassign current nodes)?
+    if ($sid > 0 && $item['status'] == 0 && $state->isActive()) {
+      $new_sid = $item['reassign'];
+      $new_state = workflow_state_load_single($new_sid);
+
+      $args = array(
+        '%workflow' => $workflow->getName(), // check_plain() is run by t().
+        '%old_state' => $state->getName(),
+        '%new_state' => isset($new_state) ? $new_state->getName() : '',
+      );
+
+      if ($item['count'] > 0) {
+        if ($form['#last_mohican']) {
+          $new_sid = NULL; // Do not reassign to new state.
+          $message = 'Removing workflow states from content in the %workflow.';
+          drupal_set_message(t($message, $args));
+        }
+        else {
+          // Prepare the state delete function.
+          $message = 'Reassigning content from %old_state to %new_state.';
+          drupal_set_message(t($message, $args));
+        }
+      }
+      // Delete the old state without orphaning nodes, move them to the new state.
+      $state->deactivate($new_sid);
+
+      $message = 'Deactivated workflow state %old_state in %workflow.';
+      watchdog('workflow', $message, $args);
+      drupal_set_message(t($message, $args));
+    }
+
+    $state->state = $item['state'];
+    $state->name = $item['name'];
+    $state->status = $item['status'];
+    $state->weight = $item['weight'];
+    $state->save();
+  }
+
+  drupal_set_message(t('The workflow was updated.'));
+  // $form_state['redirect'] = 'admin/config/workflow/workflow';
+}
+
+/**
+ * Validate duplicate machine names. Function registered in 'name' form element.
+ */
+function workflow_admin_ui_states_validate_state_machine_name($name, $element, $form_state) {
+  // @todo: Should $name be checked against DB?
+  $state_names = array();
+  foreach ($form_state['values']['states'] as $sid => $item) {
+    $state_names[] = $item['name'];
+  }
+
+  $state_names = array_map('strtolower', $state_names);
+  $result = array_unique(array_diff_assoc($state_names, array_unique($state_names)));
+
+  if (in_array($name, $result)) {
+    return TRUE;
+  }
+  return FALSE;
+}

+ 222 - 0
sites/all/modules/contrib/admin/workflow/workflow_admin_ui/workflow_admin_ui.page.transitions.inc

@@ -0,0 +1,222 @@
+<?php
+
+/**
+ * @file
+ * Provides an Admin UI page for the Workflow Transitions.
+ */
+
+/**
+ * Menu callback. Edit a workflow's transitions.
+ *
+ * @param array $transitions from values.
+ *   Transitions, for example:
+ *     18 => array(
+ *       20 => array(
+ *         'author' => 1,
+ *         1        => 0,
+ *         2        => 1,
+ *       )
+ *     )
+ *   means the transition from state 18 to state 20 can be executed by
+ *   the node author or a user in role 2. The $transitions array should
+ *   contain ALL transitions for the workflow.
+ * @param Workflow $workflow
+ *   The Workflow object.
+ *
+ * @return array
+ *   HTML form and permissions table.
+ */
+function workflow_admin_ui_transitions_form($form, &$form_state, $workflow, $op) {
+  // Make sure we have a workflow.
+  if ($workflow) {
+    $form = array();
+    $form['workflow'] = array(
+      '#type' => 'value',
+      '#value' => $workflow,
+    );
+    $form['transitions'] = _workflow_admin_ui_transition_grid_form($form, $form_state, $workflow);
+    $form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
+
+    return $form;
+  }
+}
+
+/**
+ * Form builder. Build the grid of transitions for defining a workflow.
+ *
+ * Some special situations exist:
+ * - it is not allowed to go to the '(creation)' state.
+ * - all roles are permitted to use the 'stay on this state' transition.
+ *   So, this is hidden from the user.
+ *
+ * @param Workflow $workflow
+ *   The Workflow object.
+ */
+function _workflow_admin_ui_transition_grid_form($form, &$form_state, $workflow) {
+  $form = array('#tree' => TRUE);
+
+  $states = $workflow->getStates($all = 'CREATION');
+  if (!$states) {
+    $form['error'] = array(
+      '#type' => 'markup',
+      '#value' => t('There are no states defined for this workflow.'),
+    );
+    return $form;
+  }
+
+  $roles = workflow_get_roles();
+  foreach ($states as $state1) {
+    $from = $state1->sid;
+    foreach ($states as $state2) {
+      // Don't allow transition TO '(creation)'.
+      if (!$state2->isCreationState()) {
+        $to = $state2->sid;
+        $stay_on_this_state = ($to == $from);
+
+        // Generate checkboxes for each transition.
+        foreach ($roles as $rid => $role_name) {
+          $checked = $stay_on_this_state;
+          $config_transitions = $workflow->getTransitionsBySidTargetSid($from, $to);
+          if ($config_transition = reset($config_transitions)) {
+            $checked |= $config_transition->isAllowed(array($rid));
+          }
+          $form[$from][$to][$rid] = array(
+            '#type' => $stay_on_this_state ? 'hidden' : 'checkbox',
+            '#title' => check_plain($role_name),
+            '#default_value' => $checked,
+            '#disabled' => $stay_on_this_state,
+          );
+        }
+      }
+    }
+  }
+  return $form;
+}
+
+/**
+ * Theme the workflow editing form.
+ *
+ * @see workflow_edit_form()
+ */
+function theme_workflow_admin_ui_transitions_form($variables) {
+  $output = '';
+  $form = $variables['form'];
+
+  $workflow = $form['workflow']['#value'];
+
+  if ($workflow) {
+    drupal_set_title(t('Edit workflow %name transitions', array('%name' => $workflow->getName())), PASS_THROUGH);
+
+    $states = $workflow->getStates($all = 'CREATION');
+    if ($states) {
+      $roles = workflow_get_roles();
+
+      $header = array(array('data' => t('From / To') . ' &nbsp;' . WORKFLOW_ADMIN_UI_ARROW));
+      $rows = array();
+      foreach ($states as $state) {
+        $label = check_plain($state->label());
+        // Don't allow transition TO (creation).
+        if (!$state->isCreationState()) {
+          $header[] = array('data' => $label);
+        }
+        $row = array(array('data' => $label));
+        foreach ($states as $nested_state) {
+          // Don't allow transition TO (creation).
+          if ($nested_state->isCreationState()) {
+            continue;
+          }
+          if (TRUE || $nested_state != $state) {
+            // Render checkboxes for each transition.
+            $from = $state->sid;
+            $to = $nested_state->sid;
+            $cell = '';
+            foreach ($roles as $rid => $role_name) {
+              $cell .= drupal_render($form['transitions'][$from][$to][$rid]);
+            }
+            $row[] = array('data' => $cell);
+          }
+          else {
+            $row[] = array('data' => '');
+          }
+        }
+        $rows[] = $row;
+      }
+      $output .= theme('table', array('header' => $header, 'rows' => $rows));
+    }
+    else {
+      $output = t('There are no states defined for this workflow.');
+    }
+
+    $output .= drupal_render_children($form);
+    return $output;
+  }
+}
+
+/**
+ * Validate the workflow editing form.
+ *
+ * @see workflow_edit_form()
+ */
+function workflow_admin_ui_transitions_form_validate($form, $form_state) {
+  $workflow = $form_state['values']['workflow'];
+  $wid = $workflow->wid;
+
+  // Make sure 'author' is checked for (creation) -> [something].
+  $creation_state = $workflow->getCreationState();
+  $creation_sid = $creation_state->sid;
+  if (isset($form_state['values']['transitions'][$creation_sid]) && is_array($form_state['values']['transitions'][$creation_sid])) {
+    foreach ($form_state['values']['transitions'][$creation_sid] as $roles) {
+      if ($roles[WORKFLOW_ROLE_AUTHOR_RID]) {
+        $author_has_permission = TRUE;
+        break;
+      }
+    }
+  }
+  $state_count = db_query('SELECT COUNT(sid) FROM {workflow_states} WHERE wid = :wid', array(':wid' => $wid))->fetchField();
+  if (empty($author_has_permission) && $state_count > 1) {
+    form_set_error('transitions', t('Please give the author permission to go from %creation to at least one state!',
+      array('%creation' => $creation_state->label())));
+  }
+}
+
+/**
+ * Submit handler for the workflow editing form.
+ *
+ * @see workflow_edit_form()
+ */
+function workflow_admin_ui_transitions_form_submit($form, &$form_state) {
+  $workflow = $form['workflow']['#value'];
+  $wid = $workflow->wid;
+
+  if (isset($form_state['values']['transitions'])) {
+    $transitions = $form_state['values']['transitions'];
+
+    // Empty string is sometimes passed in instead of an array.
+    if (!$transitions) {
+      return;
+    }
+    foreach ($transitions as $from => $to_data) {
+      foreach ($to_data as $to => $role_data) {
+        $roles = array();
+        foreach ($role_data as $role => $can_do) {
+          if ($can_do) {
+            $roles += array($role => $role);
+          }
+        }
+        if (count($roles)) {
+          $config_transition = $workflow->createTransition($from, $to);
+          $config_transition->roles = $roles;
+          $config_transition->save();
+        }
+        else {
+          foreach ($workflow->getTransitionsBySidTargetSid($from, $to, 'ALL') as $config_transition) {
+            $config_transition->delete();
+          }
+        }
+      }
+    }
+  }
+
+  drupal_set_message(t('The workflow was updated.'));
+  // $form_state['redirect'] = 'admin/config/workflow/workflow';
+}

+ 241 - 0
sites/all/modules/contrib/admin/workflow/workflow_admin_ui/workflow_admin_ui.page.type_map.inc

@@ -0,0 +1,241 @@
+<?php
+/**
+ * @file
+ * Provides the type_map maintenance form.
+ */
+
+/**
+ * Page builder. Show a maintenance table for type mapping.
+ *
+ * Appends the type_map form, allowing administrator
+ * to map workflows to content types and determine placement on content forms.
+ */
+function workflow_admin_ui_type_map_form(&$form) {
+  // Create list of all Workflow types. Include an initial empty value.
+  // Validate each workflow, and generate a message if not complete.
+  $workflows[0] = t('None');
+  foreach (workflow_load_multiple() as $workflow) {
+    if ($workflow->isValid()) {
+      $workflows[$workflow->wid] = $workflow->label();
+    }
+  }
+  if (count($workflows) == 1) {
+    drupal_set_message(t('You must create at least one workflow before content can be assigned to a workflow.'));
+    return $form;
+  }
+
+  $type_map = workflow_get_workflow_type_map();
+
+  $form['#tree'] = TRUE;
+
+  $form['type_map'] = array(
+    '#type' => 'fieldset',
+    // '#type' => 'container',
+    '#theme' => 'workflow_admin_ui_type_map_form',
+    '#title' => t('Content Type Mapping'),
+    '#collapsible' => TRUE,
+    '#collapsed' => FALSE,
+  );
+
+  $form['type_map']['help'] = array(
+    '#type' => 'item',
+    '#title' => '<h3>' . t('Content Type Mapping') . '</h3>',
+    '#markup' => t('Each content type may have a separate workflow. The form 
+      for changing workflow state can be displayed when editing a node, editing
+      a comment for a node, or both.'),
+  );
+
+  $placement_options = array(
+    'node' => t('Post'),
+    'comment' => t('Comment'),
+  );
+
+  foreach (node_type_get_names() as $node_type => $name) {
+    $form['type_map'][$node_type]['workflow'] = array(
+      '#type' => 'select',
+      '#options' => $workflows,
+      '#default_value' => isset($type_map[$node_type]) ? $type_map[$node_type] : 0,
+    );
+
+    $form['type_map'][$node_type]['placement'] = array(
+      '#type' => 'checkboxes',
+      '#options' => $placement_options,
+      '#default_value' => variable_get('workflow_' . $node_type, array()),
+    );
+  }
+
+  $header = array(t('Content Type cc'), t('Workflow'), t('Display Workflow Form on:'));
+  $form['type_map']['#header'] = $header;
+
+  $rows = array();
+  foreach (node_type_get_names() as $node_type => $name) {
+    $w = array(
+      '#type' => 'select',
+      '#options' => $workflows,
+      '#default_value' => isset($type_map[$node_type]) ? $type_map[$node_type] : 0,
+    );
+    $p = array(
+      '#type' => 'checkboxes',
+      '#options' => array(
+        'node' => t('Post'),
+        'comment' => t('Comment'),
+      ),
+      '#default_value' => variable_get('workflow_' . $node_type, array()),
+      '#value' => variable_get('workflow_' . $node_type, array()),
+      '#attributes' => array('style' => 'width: auto; clear: both;'),
+    );
+    // Use separate line to avoid PHP5.4 error.
+    $p_element = form_process_checkboxes($p);
+
+    $row = array(
+      check_plain(t($name)),
+      drupal_render($w),
+      drupal_render($p_element),
+    );
+    $rows[] = $row;
+
+    $form['type_map'][$node_type]['workflow_map'] = array(
+      '#theme' => 'table',
+      // '#header' => $header,
+      '#rows' => array($node_type => $row),
+      '#empty' => t('No content available.'),
+      '#attributes' => array('style' => 'width: auto; clear: both;'),
+    );
+  }
+
+  $form['type_map']['workflow_map'] = array(
+    '#theme' => 'table',
+    '#header' => $header,
+    '#rows' => $rows,
+    '#empty' => t('No content available.'),
+    // '#attributes' => array('style' => 'width: auto; clear: both;'),
+  );
+
+  $form['type_map']['workflow_access_priority'] = array(
+    '#type' => 'weight',
+    '#delta' => 10,
+    '#title' => t('Workflow Access Priority'),
+    '#default_value' => variable_get('workflow_access_priority', 0),
+    '#description' => t('This sets the node access priority. Changing this
+      setting can be dangerous. If there is any doubt, leave it at 0. 
+      <a href="@url">Read the manual.</a>', array('@url' => url('https://api.drupal.org/api/drupal/modules!node!node.api.php/function/hook_node_access_records/7'))),
+  );
+
+  $form['#submit'][] = 'workflow_admin_ui_type_map_form_submit';
+
+  return $form;
+}
+
+/**
+ * Theme the workflow type mapping form.
+ */
+function theme_workflow_admin_ui_type_map_form($variables) {
+  $output = '';
+  $form = $variables['form'];
+
+  $header = array(t('Content Type'), t('Workflow'), t('Display Workflow Form on:'));
+  $caption = t('Each content type may have a separate workflow. The form for
+    changing workflow state can be displayed when editing a node, editing a
+    comment for a node, or both.');
+
+  $rows = array();
+  foreach (node_type_get_names() as $node_type => $type_name) {
+    $rows[] = array(
+      check_plain(t($type_name)),
+      drupal_render($form[$node_type]['workflow']),
+      drupal_render($form[$node_type]['placement']),
+    );
+  }
+  $output .= theme('table', array(
+      'header' => $header,
+      'rows' => $rows,
+      'caption' => $caption,
+      // 'attributes' => array('style' => 'width: auto; clear: both;'),
+    )
+  );
+
+  return $output;
+}
+
+/**
+ * Submit handler for workflow type mapping form.
+ *
+ * Save mapping of workflow to node type. E.g., the story node type is using the Foo workflow.
+ *
+ * @see workflow_types_form()
+ */
+function workflow_admin_ui_type_map_form_submit($form, &$form_state) {
+  $form_values = $form_state['values'];
+
+  // Empty the table and the variables so that types no longer under workflow go away.
+  // @todo: it is possible to switch to new workflow for node_type, leaving nodes stuck in old workflow.
+  workflow_delete_workflow_type_map_all();
+
+  $node_types = node_type_get_names();
+  foreach ($node_types as $node_type => $type_name) {
+    $wid = $form_values['type_map'][$node_type]['workflow'];
+    variable_del('workflow_' . $node_type);
+    if ($wid) {
+      workflow_insert_workflow_type_map($node_type, $wid);
+      variable_set('workflow_' . $node_type, array_keys(array_filter(($form_values['type_map'][$node_type]['placement']))));
+
+      // If this type uses workflow, make sure pre-existing nodes are set
+      // to the workflow's creation state.
+      if ($form_values['type_map'][$node_type]['workflow']) {
+        _workflow_node_initialize_nodes('node', $node_type, $field_name = '', $wid);
+      }
+    }
+  }
+  drupal_set_message(t('The workflow mapping was saved.'));
+}
+
+/**
+ * Initialize all pre-existing nodes of a type to their first state.
+ *
+ * @param string $node_type
+ *   The node type.
+ *
+ * @todo: adjust _workflow_node_initialize_nodes() to handle Workflow Field.
+ */
+function _workflow_node_initialize_nodes($entity_type, $node_type, $field_name, $wid) {
+  global $user;
+
+  // Build the select query.
+  // We want all published nodes of this type that don't already have a workflow state.
+  $query = db_select('node', 'n');
+  $query->leftJoin('workflow_node', 'wn', 'wn.nid = n.nid');
+  // Add the fields.
+  $query->addField('n', 'nid');
+  // Add conditions.
+  $query->condition('n.type', $node_type);
+  $query->condition('n.status', 1);
+  $query->isNull('wn.sid');
+
+  $nids = $query->execute()->fetchCol();
+
+  $how_many = count($nids);
+  if ($how_many == 0) {
+    return;
+  }
+  $comment = t('Pre-existing content set to initial state.');
+
+  $workflow = workflow_load_single($wid);
+  $force = TRUE;
+  $creation_sid = $workflow->getCreationSid();
+
+  // Load them all up.
+  $nodes = node_load_multiple($nids);
+  foreach ($nodes as $entity) {
+    // Get the initial state for this entity.
+    // Due to permissions, it might be different for each user.
+    $new_sid = $workflow->getFirstSid($entity_type, $entity, $field_name, $user, $force);
+
+    $transition = new WorkflowTransition();
+    $transition->setValues($entity_type, $entity, $field_name, $creation_sid, $new_sid, $user->uid, REQUEST_TIME, $comment);
+
+    // Force it to transition to the first state and get a history record.
+    workflow_execute_transition($entity_type, $entity, $field_name, $transition, $force);
+  }
+
+  drupal_set_message(t('!count @type nodes have been initialized.', array('@type' => node_type_get_name($node_type), '!count' => $how_many)));
+}

+ 276 - 0
sites/all/modules/contrib/admin/workflow/workflow_admin_ui/workflow_admin_ui.page.workflow.inc

@@ -0,0 +1,276 @@
+<?php
+
+/**
+ * @file
+ * Provides an Admin UI page for the Workflow Properties.
+ */
+
+/**
+ * Menu callback. Edit a workflow's properties.
+ *
+ * @param Workflow $worflow
+ *   The workflow object.
+ *
+ * @return array
+ *   HTML form and permissions table.
+ */
+function workflow_admin_ui_edit_form($form, &$form_state, $workflow = NULL, $op) {
+  $noyes = array(t('No'), t('Yes'));
+  $fieldset_options = array(0 => t('No fieldset'), 1 => t('Collapsible fieldset'), 2 => t('Collapsed fieldset'));
+
+  $form = array();
+
+  $form['workflow'] = array(
+    '#type' => 'value',
+    '#value' => $workflow,
+  );
+
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+    '#submit' => array('workflow_admin_ui_edit_form_submit'),
+    '#validate' => array('workflow_admin_ui_edit_form_validate'),
+    '#weight' => 15,
+  );
+  $form['label'] = array(
+    '#title' => t('Label'),
+    '#type' => 'textfield',
+    '#default_value' => isset($workflow->label) ? $workflow->label : $workflow->name,
+    '#description' => t('The human-readable name of this content type.'),
+    '#required' => TRUE,
+    '#maxlength' => '254',
+    '#size' => 30,
+  );
+  $form['name'] = array(
+    '#type' => 'machine_name',
+    '#default_value' => $workflow->getName(),
+    '#maxlength' => '254', // $todo D8 : '#maxlength' => 32,
+    '#required' => TRUE,
+    '#disabled' => !empty($workflow->locked),
+    '#machine_name' => array(
+      'exists' => 'workflow_load',
+      'source' => array('label'),
+    ),
+    '#description' => t('A unique machine-readable name for this content type. It must only contain lowercase letters, numbers, and underscores.'),
+  );
+
+  // All other settings are only for workflow_node.
+  if (!module_exists('workflownode')) {
+    return $form; // <==== exit !
+  }
+
+  $form['basic'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Workflow form settings'),
+  );
+
+  $form['basic']['fieldset'] = array(
+    '#type' => 'select',
+    '#options' => $fieldset_options,
+    '#title' => t('Show the form in a fieldset?'),
+    '#default_value' => isset($workflow->options['fieldset']) ? $workflow->options['fieldset'] : 0,
+    '#description' => t("The Widget can be wrapped in a visible fieldset. You'd
+      do this when you use the widget on a Node Edit page."
+    ),
+  );
+  $form['basic']['options'] = array(
+    '#type' => 'select',
+    '#title' => t('How to show the available states'),
+    '#required' => FALSE,
+    '#default_value' => isset($workflow->options['options']) ? $workflow->options['options'] : 'radios',
+    // '#multiple' => TRUE / FALSE,
+    '#options' => array(
+      // These options are taken from options.module
+      'select' => 'Select list',
+      'radios' => 'Radio buttons',
+      // This option does not work properly on Comment Add form.
+      'buttons' => 'Action buttons',
+    ),
+    '#description' => t("The Widget shows all available states. Decide which
+      is the best way to show them."
+    ),
+  );
+  $form['basic']['name_as_title'] = array(
+    '#type' => 'radios',
+    '#options' => $noyes,
+    '#attributes' => array('class' => array('container-inline')),
+    '#title' => t('Use the workflow name as the title of the workflow form?'),
+    '#default_value' => isset($workflow->options['name_as_title']) ? $workflow->options['name_as_title'] : 0,
+    '#description' => t('The workflow section of the editing form is in its own
+      fieldset. Checking the box will add the workflow name as the title of workflow section of the editing form.'),
+  );
+
+  $form['schedule'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Scheduling for Workflow'),
+  );
+
+  $form['schedule']['schedule'] = array(
+    '#type' => 'radios',
+    '#options' => $noyes,
+    '#attributes' => array('class' => array('container-inline')),
+    '#title' => t('Allow scheduling of workflow transitions?'),
+    '#default_value' => isset($workflow->options['schedule']) ? $workflow->options['schedule'] : 1,
+    '#description' => t('Workflow transitions may be scheduled to a moment in the future.
+      Soon after the desired moment, the transition is executed by Cron.'),
+  );
+
+  $form['schedule']['schedule_timezone'] = array(
+    '#type' => 'radios',
+    '#options' => $noyes,
+    '#attributes' => array('class' => array('container-inline')),
+    '#title' => t('Show a timezone when scheduling a transition?'),
+    '#default_value' => isset($workflow->options['schedule_timezone']) ? $workflow->options['schedule_timezone'] : 1,
+  );
+
+  $form['comment'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Comment for Workflow Log'),
+  );
+
+  $form['comment']['comment_log_node'] = array(
+    '#type' => 'select',
+    '#required' => FALSE,
+    '#options' => array(
+      // Use 0/1/2 to stay compatible with previous checkbox.
+      0 => t('hidden'),
+      1 => t('optional'),
+      2 => t('required'),
+    ),
+    '#attributes' => array('class' => array('container-inline')),
+    '#title' => t('Show a comment field in the workflow section of the editing form?'),
+    '#default_value' => isset($workflow->options['comment_log_node']) ? $workflow->options['comment_log_node'] : 0,
+    '#description' => t(
+      'On the node editing form, a Comment form can be shown so that the person
+      making the state change can record reasons for doing so. The comment is
+      then included in the node\'s workflow history.'
+    ),
+  );
+
+  $form['comment']['comment_log_tab'] = array(
+    '#type' => 'select',
+    '#required' => FALSE,
+    '#options' => array(
+      // Use 0/1/2 to stay compatible with previous checkbox.
+      0 => t('hidden'),
+      1 => t('optional'),
+      2 => t('required'),
+    ),
+    '#attributes' => array('class' => array('container-inline')),
+    '#title' => t('Show a comment field in the workflow section of the workflow tab form?'),
+    '#default_value' => isset($workflow->options['comment_log_tab']) ? $workflow->options['comment_log_tab'] : 0,
+    '#description' => t(
+      'On the workflow tab, a Comment form can be shown so that the person
+      making the state change can record reasons for doing so. The comment
+      is then included in the node\'s workflow history.'
+    ),
+  );
+
+  $form['watchdog'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Watchdog Logging for Workflow'),
+  );
+
+  $form['watchdog']['watchdog_log'] = array(
+    '#type' => 'radios',
+    '#options' => $noyes,
+    '#attributes' => array('class' => array('container-inline')),
+    '#title' => t('Log informational watchdog messages when a transition is executed (state of a node is changed)?'),
+    '#default_value' => isset($workflow->options['watchdog_log']) ? $workflow->options['watchdog_log'] : 0,
+    '#description' => t('Optionally log transition state changes to watchdog.'),
+  );
+
+  $form['tab'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Workflow tab permissions'),
+    '#collapsible' => TRUE,
+    '#collapsed' => FALSE,
+  );
+  $form['tab']['history_tab_show'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Use the workflow history, and show it on a separate tab.'),
+    '#required' => FALSE,
+    '#default_value' => isset($workflow->options['history_tab_show']) ? $workflow->options['history_tab_show'] : 1,
+    '#description' => t("Every state change is recorded in table
+      {workflow_node_history}. If checked and user has proper permission, a
+      tab 'Workflow' is shown on the entity view page, which gives access to
+      the History of the workflow."),
+  );
+  $form['tab']['tab_roles'] = array(
+    '#type' => 'checkboxes',
+    '#options' => workflow_get_roles(),
+    '#default_value' => array_keys($workflow->tab_roles),
+    '#description' => t('Select any roles that should have access to the workflow tab on nodes that have a workflow.'),
+  );
+
+  return $form;
+}
+
+/**
+ * Validate the workflow edit/add form.
+ */
+function workflow_admin_ui_edit_form_validate($form, &$form_state) {
+  $workflow = $form_state['values']['workflow'];
+  $name = $form_state['values']['name'];
+
+  // Make sure workflow name is not numeric.
+  if (ctype_digit($name)) {
+    form_set_error('name', t('Please choose a non-numeric name for your workflow.',
+      array('%name' => $name)));
+  }
+
+  // Make sure workflow name is not a duplicate.
+  foreach (workflow_load_multiple() as $stored_workflow) {
+    if ($name == check_plain($stored_workflow->getName()) && $workflow->wid != $stored_workflow->wid) {
+      form_set_error('name', t('A workflow with the name %name already exists. Please enter another name for this workflow.',
+        array('%name' => $name)));
+      break;
+    }
+  }
+}
+
+/**
+ * Submit handler for the workflow editing form.
+ *
+ * @see workflow_edit_form()
+ * @todo: this is only valid for Node API, not for Field API.
+ *        Field API has 'Field settings'.
+ */
+function workflow_admin_ui_edit_form_submit($form, &$form_state) {
+  $workflow = $form_state['values']['workflow'];
+  $insert = !empty($workflow->is_new);
+
+  $workflow->name = trim($form_state['values']['name']);
+  $workflow->label = trim($form_state['values']['label']);
+  // For workflow_field, all is in the field settings.
+  // All other settings are only for workflow_node.
+  if (module_exists('workflownode')) {
+    $workflow->tab_roles = array_filter($form_state['values']['tab_roles']);
+    $workflow->options = array(
+      'name_as_title' => $form_state['values']['name_as_title'],
+      'options' => $form_state['values']['options'],
+      'schedule' => $form_state['values']['schedule'],
+      'schedule_timezone' => $form_state['values']['schedule_timezone'],
+      'comment_log_node' => $form_state['values']['comment_log_node'],
+      'comment_log_tab' => $form_state['values']['comment_log_tab'],
+      'watchdog_log' => $form_state['values']['watchdog_log'],
+      'history_tab_show' => $form_state['values']['history_tab_show'],
+    );
+  }
+
+  $workflow->save();
+  if ($insert) {
+    $args = array(
+      '%name' => $workflow->getName(),
+      '@url' => url('admin/config/workflow/workflow/edit/' . $workflow->wid),
+    );
+    watchdog('workflow', 'Created workflow %name', $args);
+    drupal_set_message(t('The workflow %name was created. Please maintain the states and transitions.', $args), 'status');
+  }
+  else {
+    drupal_set_message(t('The workflow was updated.'));
+  }
+  // This redirect is needed, when changing the workflow name, with name in URL.
+  // Also for cloning a workflow.
+  $form_state['redirect'] = 'admin/config/workflow/workflow/manage/' . $workflow->wid;
+}

+ 5 - 4
sites/all/modules/contrib/admin/workflow/workflow_cleanup/workflow_cleanup.info

@@ -1,12 +1,13 @@
 name = Workflow Clean Up
-description = "Cleans up Workflow cruft"
+description = "Cleans up Workflow cruft."
 dependencies[] = workflow
 core = 7.x
 package = Workflow
 
-; Information added by drupal.org packaging script on 2013-07-04
-version = "7.x-1.2"
+configure = admin/config/workflow/workflow/cleanup
+; Information added by Drupal.org packaging script on 2017-01-15
+version = "7.x-2.9+10-dev"
 core = "7.x"
 project = "workflow"
-datestamp = "1372980654"
+datestamp = "1484510888"
 

+ 19 - 182
sites/all/modules/contrib/admin/workflow/workflow_cleanup/workflow_cleanup.module

@@ -11,200 +11,37 @@ function workflow_cleanup_menu() {
   $items = array();
 
   $items['admin/config/workflow/workflow/cleanup'] = array(
-    'title' => 'Workflow Clean Up',
+    'title' => 'Clean up workflow',
+    'file' => 'workflow_cleanup.pages.inc',
     'access arguments' => array('administer workflow'),
     'page callback' => 'drupal_get_form',
     'page arguments' => array('workflow_cleanup_form'),
-    'type' => MENU_CALLBACK,
-    );
+    'type' => MENU_LOCAL_ACTION,
+  );
 
   return $items;
 }
 
 /**
- * Implements hook_theme().
- */
-function workflow_cleanup_theme() {
-  return array(
-    'workflow_cleanup_form' => array('render element' => 'form'),
-    );
-}
-
-/**
- * Implements hook_workflow_operations().
- * Might as well eat our own cooking.
- */
-function workflow_cleanup_workflow_operations($op, $workflow = NULL, $state = NULL) {
-  switch ($op) {
-    case 'top_actions':
-      $workflows = workflow_get_workflows();
-      $alt = t('Clean up workflow cruft');
-      $actions = array(
-        'workflow-cleanup' => array(
-          'title' => t('Clean up'),
-          'href' => 'admin/config/workflow/workflow/cleanup',
-          'attributes' => array('alt' => $alt, 'title' => $alt),
-          ),
-        );
-
-      return $actions;
-  }
-}
-
-/**
- * The main cleanup page.
- */
-function workflow_cleanup_form($form, $form_state) {
-  $bc = array(l(t('Home'), '<front>'));
-  $bc[] = l(t('Configuration'), 'admin/config');
-  $bc[] = l(t('Workflow'), 'admin/config/workflow');
-  $bc[] = l(t('Workflow'), 'admin/config/workflow/workflow');
-  drupal_set_breadcrumb($bc);
-
-  $form = array();
-
-  // Get all of the states, indexed by sid.
-  $states = $orphans = $inactive = array();
-  foreach (workflow_get_workflow_states() as $state) {
-    $states[$state->sid] = $state;
-    // Is it associated with a workflow?
-    if (empty($state->name)) {
-      $orphans[$state->sid] = $state->state;
-    }
-    else {
-      // Is it associated with a workflow?
-      if (!$state->status) {
-        $inactive[$state->sid] = $state->state;
-      }
-    }
-  }
-
-  // Deal with no orphan states.
-  if (!$orphans) {
-    $orphans[0] = t('None');
-  }
-
-  // Deal with no inactive states.
-  if (!$inactive) {
-    $inactive[0] = $states[0] = t('None');
-  }
-
-  $form['#workflow_states'] = $states;
-
-  $form['no_workflow'] = array(
-    '#type' => 'container',
-    '#title' => t('Orphaned States'),
-    '#description' => t('These states no longer belong to an existing workflow.'),
-    '#tree' => TRUE,
-    );
-
-  foreach ($orphans as $sid => $state) {
-    $form['no_workflow'][$sid]['check'] = array(
-      '#type' => 'checkbox',
-      '#return_value' => $sid,
-      );
-
-    $form['no_workflow'][$sid]['name'] = array(
-      '#type' => 'markup',
-      '#markup' => check_plain($state),
-      );
-  }
-
-  $form['inactive'] = array(
-    '#type' => 'container',
-    '#title' => t('Inactive (Deleted) States'),
-    '#description' => t('These states belong to a workflow, but have been marked inactive (deleted).'),
-    '#tree' => TRUE,
-    );
-
-  foreach ($inactive as $sid => $state) {
-    $form['inactive'][$sid]['check'] = array(
-      '#type' => 'checkbox',
-      '#return_value' => $sid,
-      );
-
-    $form['inactive'][$sid]['name'] = array(
-      '#type' => 'markup',
-      '#markup' => check_plain($state),
-      );
-
-    $form['inactive'][$sid]['wf'] = array(
-      '#type' => 'markup',
-      '#markup' => check_plain($states[$sid]->name),
-      );
-  }
-
-  $form['submit'] = array('#type' => 'submit', '#value' => t('Delete selected states'));
-
-  return $form;
-}
-
-/**
- * Theme the main form.
+ * Implements hook_help().
  */
-function theme_workflow_cleanup_form($variables) {
-  $form = $variables['form'];
-  $output = '';
-  $header = array(t('select'), t('State'));
-
-  $rows = array();
-  foreach (element_children($form['no_workflow']) as $sid) {
-    $rows[] = array(
-      drupal_render($form['no_workflow'][$sid]['check']),
-      drupal_render($form['no_workflow'][$sid]['name']),
-      );
+function workflow_cleanup_help($path, $arg) {
+  switch ($path) {
+    case 'admin/config/workflow/workflow/cleanup':
+      return t('This page allows you to delete orphaned and inactive states.
+        States can be deleted freely in a development environment, but be
+        careful if you have used a State in a production environment. The
+        transition history of your content will loose the description of a
+        previously used state. If your Workflow must comply to some auditing
+        standards, you should NOT use this function.');
   }
-
-  $output .= '<h3>' . $form['no_workflow']['#title'] . '</h3>';
-  $output .= '<div class="description">' . $form['no_workflow']['#description'] . '</div>';
-  $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('style' => 'width: auto;')));
-
-  $header[] = t('Workflow');
-  $rows = array();
-  foreach (element_children($form['inactive']) as $sid) {
-    $rows[] = array(
-      drupal_render($form['inactive'][$sid]['check']),
-      drupal_render($form['inactive'][$sid]['name']),
-      drupal_render($form['inactive'][$sid]['wf']),
-      );
-  }
-
-  $output .= '<h3>' . $form['inactive']['#title'] . '</h3>';
-  $output .= '<div class="description">' . $form['inactive']['#description'] . '</div>';
-  $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('style' => 'width: auto;')));
-
-  $output .= drupal_render_children($form);
-  return $output;
 }
 
 /**
- * Submission handler for main cleanup form.
+ * Implements hook_theme().
  */
-function workflow_cleanup_form_submit($form, $form_state) {
-  $states = $form['#workflow_states'];
-  foreach (array('no_workflow', 'inactive') as $section) {
-    foreach ($form_state['values'][$section] as $sid => $data) {
-      // FAPI returns either a 0 or the sid.
-      if ($data['check']) {
-        // Delete any transitions this state is involved in.
-        $trans_del = db_delete('workflow_transitions')->condition('target_sid', $sid)->execute();
-        $trans_del += db_delete('workflow_transitions')->condition('sid', $sid)->execute();
-        if ($trans_del) {
-          drupal_set_message(t('@count transitions for the "@state" state have been deleted.',
-            array('@state' => $states[$sid]->state, '@count' => $trans_del)));
-        }
-
-        // Remove history records too.
-        $hist_del = db_delete('workflow_node_history')->condition('sid', $sid)->execute();
-        if ($hist_del) {
-          drupal_set_message(t('@count history records for the "@state" state have been deleted.',
-            array('@state' => $states[$sid]->state, '@count' => $hist_del)));
-        }
-
-        // Go ahead and delete the state.
-        db_delete('workflow_states')->condition('sid', $sid)->execute();
-        drupal_set_message(t('The "@state" state has been deleted.', array('@state' => $states[$sid]->state)));
-      }
-    }
-  }
+function workflow_cleanup_theme() {
+  return array(
+    'workflow_cleanup_form' => array('render element' => 'form'),
+  );
 }

+ 166 - 0
sites/all/modules/contrib/admin/workflow/workflow_cleanup/workflow_cleanup.pages.inc

@@ -0,0 +1,166 @@
+<?php
+/**
+ * @file
+ * Contains admin/config/workflow/workflow/cleanup page.
+ */
+
+/**
+ * The main cleanup page.
+ */
+function workflow_cleanup_form($form, $form_state) {
+  $form = array();
+
+  // Get all of the states, indexed by sid.
+  $states = $orphans = $inactive = array();
+
+  foreach ($states = workflow_state_load_multiple() as $state) {
+    // Does the associated workflow exist?
+    if (!$state->getWorkflow()) {
+      $orphans[$state->sid] = $state->getName();
+    }
+    else {
+      // Is the state still active?
+      if (!$state->isActive()) {
+        $inactive[$state->sid] = $state->getName();
+      }
+    }
+  }
+
+  $form['#workflow_states'] = $states;
+
+  $form['no_workflow'] = array(
+    '#type' => 'container',
+    '#title' => t('Orphaned States'),
+    '#description' => t('These states no longer belong to an existing workflow.'),
+    '#tree' => TRUE,
+  );
+
+  foreach ($orphans as $sid => $name) {
+    $form['no_workflow'][$sid]['check'] = array(
+      '#type' => 'checkbox',
+      '#return_value' => $sid,
+    );
+
+    $form['no_workflow'][$sid]['name'] = array(
+      '#type' => 'markup',
+      '#markup' => check_plain($name),
+    );
+  }
+
+  $form['inactive'] = array(
+    '#type' => 'container',
+    '#title' => t('Inactive (Deleted) States'),
+    '#description' => t('These states belong to a workflow, but have been marked inactive (deleted).'),
+    '#tree' => TRUE,
+  );
+
+  foreach ($inactive as $sid => $name) {
+    $form['inactive'][$sid]['check'] = array(
+      '#type' => 'checkbox',
+      '#return_value' => $sid,
+    );
+
+    $form['inactive'][$sid]['name'] = array(
+      '#type' => 'markup',
+      '#markup' => check_plain($name),
+    );
+
+    $form['inactive'][$sid]['wf'] = array(
+      '#type' => 'markup',
+      '#markup' => (!empty($sid)) ? check_plain($states[$sid]->getWorkflow()->getName()) : '',
+    );
+  }
+
+  $form['submit'] = array('#type' => 'submit', '#value' => t('Delete selected states'));
+
+  return $form;
+}
+
+/**
+ * Theme the main form.
+ */
+function theme_workflow_cleanup_form($variables) {
+  $form = $variables['form'];
+  $output = '';
+  $header = array(t('Select'), t('State'));
+
+  $rows = array();
+  foreach (element_children($form['no_workflow']) as $sid) {
+    $rows[] = array(
+      drupal_render($form['no_workflow'][$sid]['check']),
+      drupal_render($form['no_workflow'][$sid]['name']),
+    );
+  }
+
+  $output .= '<h3>' . $form['no_workflow']['#title'] . '</h3>';
+  $output .= '<div class="description">' . $form['no_workflow']['#description'] . '</div>';
+  $output .= theme('table', array(
+    'header' => $header,
+    'rows' => $rows,
+    'empty' => 'All states are fine',
+    'attributes' => array('style' => 'width: auto;'),
+  ));
+
+  $header[] = t('Workflow');
+  $rows = array();
+  foreach (element_children($form['inactive']) as $sid) {
+    $rows[] = array(
+      drupal_render($form['inactive'][$sid]['check']),
+      drupal_render($form['inactive'][$sid]['name']),
+      drupal_render($form['inactive'][$sid]['wf']),
+    );
+  }
+
+  $output .= '<h3>' . $form['inactive']['#title'] . '</h3>';
+  $output .= '<div class="description">' . $form['inactive']['#description'] . '</div>';
+  $output .= theme(
+    'table',
+    array(
+      'header' => $header,
+      'rows' => $rows,
+      'empty' => 'All states are fine',
+      'attributes' => array('style' => 'width: auto;'),
+    )
+  );
+
+  $output .= drupal_render_children($form);
+  return $output;
+}
+
+/**
+ * Submission handler for main cleanup form.
+ */
+function workflow_cleanup_form_submit($form, $form_state) {
+  $states = $form['#workflow_states'];
+  foreach (array('no_workflow', 'inactive') as $section) {
+    if (!isset($form_state['values'][$section])) {
+      continue;
+    }
+    foreach ($form_state['values'][$section] as $sid => $data) {
+      // FAPI returns either a 0 or the sid.
+      if ($data['check']) {
+        $state = $states[$sid];
+        $state_name = $state->getName();
+        // Delete any transitions this state is involved in.
+        $trans_del = db_delete('workflow_transitions')->condition('target_sid', $sid)->execute();
+        $trans_del += db_delete('workflow_transitions')->condition('sid', $sid)->execute();
+        if ($trans_del) {
+          drupal_set_message(t('@count transitions for the "@state" state have been deleted.',
+            array('@state' => $state_name, '@count' => $trans_del)));
+        }
+
+        // Remove history records too.
+        $hist_del = db_delete('workflow_node_history')->condition('sid', $sid)->execute();
+        if ($hist_del) {
+          drupal_set_message(t('@count history records for the "@state" state have been deleted.',
+            array('@state' => $state_name, '@count' => $hist_del)));
+        }
+
+        // Go ahead and delete the state.
+        db_delete('workflow_states')->condition('sid', $sid)->execute();
+        drupal_set_message(t('The "@state" state has been deleted.',
+          array('@state' => $state_name)));
+      }
+    }
+  }
+}

+ 26 - 0
sites/all/modules/contrib/admin/workflow/workflow_field/README.txt

@@ -0,0 +1,26 @@
+This is a re-implementation of the Workflow module, using the Field API instead of the Form API.
+
+The field definition (and widget and formatters) is implemented as lazy-loading classes in the main workflow module.
+The activation of the Field type is done in the submodule Workflow_field, 
+as stated in https://drupal.org/node/1285540 "Field types should be defined by one module and implemented by a separate module."
+
+ONLY USE THIS MODULE IF: 
+- you are happy with the features the Workflow core API provides
+  (not all persons may choose from all possible values at all moments.)
+- you want to test and help developing this submodule.
+
+The current version supports: 
+- the default Workflow API. 
+- Workflow Admin UI, which manages CRUD for Workflows, States and Transitions.
+- Workflow Access, since this works via Workflow API.
+
+The current version provides: 
+- adding a Workflow Field on an Entity type (Node type), or a Node Comment;
+- usage of the core formatter from the List module (just showing the description of the current value);
+- usage of the core widgets from the Options module (select list, radio buttons);
+- usage of the usual Workflow Form, which contains also a Comment text area and Scheduling options.
+- changing the 'Workflow state' value on a Node Edit page.
+- changing the 'Workflow state' value via a Node's Comment.
+
+The current version DOES NOT provide: 
+- support for other submodules from the Workflow module. (At least, this is not tested.)

+ 314 - 0
sites/all/modules/contrib/admin/workflow/workflow_field/workflowfield.field.inc

@@ -0,0 +1,314 @@
+<?php
+
+/**
+ * @file
+ * Defines a Workflow field, widget and formatter. (copied from list field).
+ */
+
+/**
+ * Implements hook_field_info().
+ */
+function workflowfield_field_info() {
+  return WorkflowItem::getInfo();
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ *
+ * Changes the hook_field_settings_form.
+ * Fixes some Field settings and Field Instance settings, and makes sure users cannot change it.
+ *
+ * @todo: perhaps this is core functionality, but these values are only saved
+ *        when the site builder explicitly save the instance settings. :-(
+ */
+function workflowfield_form_field_ui_field_edit_form_alter(&$form, &$form_state, $form_id) {
+  if ($form['#field']['type'] == 'workflow') {
+    // The Workflow field must have a value, so set to required.
+    $form['instance']['required']['#default_value'] = 1;
+    $form['instance']['required']['#disabled'] = TRUE;
+
+    // User may not set a default value (this is done by the Workflow module).
+    // @see WorkflowState::getOptions()
+    $form['instance']['default_value_widget']['#type'] = 'hidden';
+    $form['instance']['default_value_widget']['#disabled'] = TRUE;
+    unset($form['instance']['default_value_widget']);
+
+    // Make sure only 1 value can be entered in the Workflow field.
+    $form['field']['cardinality']['#default_value'] = 1;
+    $form['field']['cardinality']['#disabled'] = TRUE;
+  }
+}
+
+/**
+ * Implements hook_field_settings_form().
+ */
+function workflowfield_field_settings_form($field, $instance, $has_data) {
+  $form = array();
+  $form_state = array();
+
+  $workflow_field = new WorkflowItem($field, $instance);
+  return $workflow_field->settingsForm($form, $form_state, $has_data);
+}
+
+/**
+ * Implements property_callbacks for hook_field_info().
+ */
+function workflowfield_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) {
+  $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
+  $property['getter callback'] = 'entity_metadata_field_property_get';
+  $property['getter callback'] = '_workflowfield_metadata_property_get';
+  // $property['setter callback'] = 'entity_metadata_field_property_set';
+  // $property['setter callback'] = '_workflowfield_metadata_property_set';
+  $property['options list'] = 'entity_metadata_field_options_list';
+  $property['property info'] = array(
+    'value' => array(
+      'type' => 'integer',
+      'label' => t('State ID'),
+      'setter callback' => 'entity_property_verbatim_set',
+    ),
+    'workflow' => array(
+      'type' => 'array',
+      'label' => t('Workflow details'),
+      'setter callback' => 'entity_property_verbatim_set',
+    ),
+  );
+}
+
+/**
+ * Getter callback for Workflow defined in hook_entity_property_info_alter.
+ *
+ * This is different from the default, because 'value' is not always set
+ * and 'workflow' may be set, but is not in the field data.
+ */
+function _workflowfield_metadata_property_get($entity, array $options, $name, $entity_type, $info) {
+  $values = array();
+
+  // return entity_metadata_field_property_get($entity, array $options, $name, $entity_type, $info);
+  $field = field_info_field($name);
+  $columns = array_keys($field['columns']);
+  $langcode = isset($options['language']) ? $options['language']->language : LANGUAGE_NONE;
+  $langcode = entity_metadata_field_get_language($entity_type, $entity, $field, $langcode, TRUE);
+
+  if (isset($entity->{$name}[$langcode])) {
+    foreach ($entity->{$name}[$langcode] as $delta => $data) {
+      // In workflowfield_property_info_callback(), we needed to set a column 'value'.
+      // This is now filled from the widget data.
+      // Sometimes that is not widget, or the submit function has not been processed yet.
+
+      // On a normal widget:
+      $sid = isset($data['value']) ? $data['value'] : 0;
+      // On a workflow form widget:
+      $sid = isset($data['workflow']['workflow_sid']) ? $data['workflow']['workflow_sid'] : $sid;
+
+      // The workflow widget was not loaded properly. @see #2597307.
+      // So we need to reload the entity to extract the correct value.
+      if (!$sid && isset($data['workflow'])) {
+        // $new_entity = $entity->original; // is NULL :-(
+        list($entity_id, , ) = entity_extract_ids($entity_type, $entity);
+        if (!empty($entity_id)) {
+          $new_entity = entity_load_single($entity_type, $entity_id);
+          $workflow = $new_entity->{$name}[$langcode][$delta];
+          $sid = isset($workflow['value']) ? (int)$workflow['value'] : 0;
+        }
+      }
+
+      $data[$columns[0]] = $sid;
+      $values[$delta] = $sid;
+    }
+  }
+  // For an empty single-valued field, we have to return NULL.
+  return $field['cardinality'] == 1 ? ($values ? reset($values) : NULL) : $values;
+}
+
+/**
+ * Callback for setting field property values.
+ */
+function _workflowfield_metadata_property_set($entity, $name, $value, $langcode, $entity_type, $info) {
+  // return entity_metadata_field_property_set($entity, $name, $value, $langcode, $entity_type, $info);
+  $field = field_info_field($name);
+  $columns = array_keys($field['columns']);
+  $langcode = entity_metadata_field_get_language($entity_type, $entity, $field, $langcode);
+  $values = $field['cardinality'] == 1 ? array($value) : (array) $value;
+}
+
+
+/**
+ * We will be using some default formatters and widgets from the List and Options modules.
+ */
+
+/**
+ * Implements hook_field_formatter_info_alter().
+ *
+ * The module reuses the formatters defined in list.module.
+ */
+function workflowfield_field_formatter_info_alter(&$info) {
+  $info['list_default']['field types'][] = 'workflow';
+}
+
+/**
+ * Implements hook_field_widget_info_alter().
+ *
+ * The module does not implement widgets of its own, but reuses the
+ * widgets defined in options.module.
+ *
+ * @see workflowfield_options_list()
+ */
+function workflowfield_field_widget_info_alter(&$info) {
+  $info['options_select']['field types'][] = 'workflow';
+  $info['options_buttons']['field types'][] = 'workflow';
+}
+
+/**
+ * Do not implement hook_field_presave(),
+ * since $nid is needed, but not yet known at this moment.
+ */
+/*
+// function workflowfield_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
+// }
+ */
+
+/**
+ * Implements hook_field_insert().
+ */
+function workflowfield_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
+  return workflowfield_field_update($entity_type, $entity, $field, $instance, $langcode, $items);
+}
+
+/**
+ * Implements hook_field_update().
+ *
+ * It is the D7-wrapper for D8-style WorkflowDefaultWidget::submit.
+ * It is called also from hook_field_insert, since we need $nid to store workflow_node_history.
+ * We cannot use hook_field_presave, since $nid is not yet known at that moment.
+ */
+function workflowfield_field_update($entity_type, $entity, array $field, $instance, $langcode, &$items) {
+  $form = array();
+  $form_state = array();
+  $field_name = $field['field_name'];
+
+  if ($entity_type == 'comment') {
+    // This happens when we are on an entity's comment.
+    // We save the field of the node. The comment is saved automatically.
+    $referenced_entity_type = 'node'; // Comments only exist on nodes.
+    $referenced_entity_id = $entity->nid;
+    // Load the node again, since the passed node doesn't contain proper 'type' field.
+    $referenced_entity = entity_load_single($referenced_entity_type, $referenced_entity_id);
+    // Normalize the contents of the workflow field.
+    $items[0]['value'] = _workflow_get_sid_by_items($items);
+
+    // Execute the transition upon the node. Afterwards, $items is in form as expected by Field API.
+    // Remember, we don't know if the transition is scheduled or not.
+    $widget = new WorkflowTransitionForm($field, $instance, $referenced_entity_type, $referenced_entity);
+    $widget->submitForm($form, $form_state, $items); // $items is a proprietary D7 parameter.
+    // // Since we are saving the comment only, we must save the node separately.
+    // entity_save($referenced_entity_type, $referenced_entity);
+  }
+  else {
+    $widget = new WorkflowTransitionForm($field, $instance, $entity_type, $entity);
+    $widget->submitForm($form, $form_state, $items); // $items is a proprietary D7 parameter.
+  }
+}
+
+/**
+ * Implements hook_field_prepare_view().
+ *
+ * This hook is needed in the following case:
+ * - Edit a node with Workflow Field;
+ * - Change the value of the widget;
+ * - Click [Preview] button;
+ * See the "Notice: Undefined index: value in list_field_formatter_view() (line 467+472 of \modules\field\modules\list\list.module)."
+ * This is because in the Workflow Widget, the data is stored in a 'workflow'
+ * structure, not a 'value' value.
+ *
+ * However, it is a pity that we run this hook in every view :-(
+ */
+function workflowfield_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) {
+  // I guess Preview mode is only with 1 entity at a time.
+  if (count ( $items ) !== 1) {
+    return;
+  }
+
+  foreach ($items as $id => &$item_array) {
+    foreach ($item_array as $index => &$item) {
+      if (!isset($item['value'])) {
+        $item['value'] = isset($item['workflow']['workflow_sid']) ? $item['workflow']['workflow_sid'] : '';
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_field_delete().
+ *
+ * @todo: implement
+ */
+function workflowfield_field_delete($entity_type, $entity, $field, $instance, $langcode, &$items) {
+  $workflow_field = new WorkflowItem($field, $instance, $entity_type, $entity);
+  $workflow_field->delete($items);
+}
+
+/**
+ * Implements hook_field_is_empty().
+ *
+ * The Workflow field is never empty.
+ */
+function workflowfield_field_is_empty($item, $field) {
+  // $workflow_field = new WorkflowItem($field, $instance, $entity_type, $entity);
+  // $workflow_field->isEmpty($item);
+  return FALSE;
+}
+
+/**
+ * Implements hook_field_delete_field().
+ *
+ * @todo: implement functionality from workflow_node_delete().
+ */
+/*
+// function workflowfield_field_delete_field($entity_type, $entity, $field, $instance, $langcode, &$items) {
+// }
+ */
+
+/**
+ * Callback function for list.module formatter.
+ *
+ * Returns array of allowed values when using a 'core' list formatter,
+ * instead of the 'workflow' formatter.
+ *
+ * @see list_allowed_values()
+ */
+function workflowfield_allowed_values($field, $instance, $entity_type, $entity) {
+  $workflow_field = new WorkflowItem($field, $instance, $entity_type, $entity);
+  return $workflow_field->getAllowedValues();
+}
+
+/**
+ * Implements hook_options_list().
+ *
+ * Callback function for the default Options widgets.
+ *
+ * @todo: move to a class.
+ */
+function workflowfield_options_list($field, $instance, $entity_type, $entity) {
+  global $user; // @todo #2287057: OK?
+  // @todo: Perhaps global user is not always the correct user.
+  // E.g., on ScheduledTransition->execute()? But this function is mostly used in UI.
+
+  $options = array();
+
+  if ($entity) {
+    // Get the allowed new states for the entity's current state.
+    $field_name = $field['field_name'];
+    $sid = workflow_node_current_state($entity, $entity_type, $field_name);
+    $state = workflow_state_load_single($sid);
+    $options = $state->getOptions($entity_type, $entity, $field_name, $user, FALSE);
+  }
+  else {
+    // $field['settings']['wid'] can be numeric or named.
+    if ($workflow = workflow_load_single($field['settings']['wid'])) {
+      // There is no entity, E.g., on the Rules action "Set a data value".
+      $state = new WorkflowState(array('wid' => $workflow->wid));
+      $options = $state->getOptions('', NULL, '', $user, FALSE);
+    }
+  }
+  return $options;
+}

+ 68 - 0
sites/all/modules/contrib/admin/workflow/workflow_field/workflowfield.formatter.inc

@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Defines a Workflow formatter.
+ * You won't find a DefaultFormatter, because:
+ * - The 'default' formatter provided by the List module;
+ * - The 'workflow' formatter is only representing the WorkflowDefault Widget.
+ *
+ * All hooks are wrapper functions for a D8-style WorkflowDefaultWidget object.
+ */
+
+/**
+ * Implements hook_field_formatter_info().
+ */
+function workflowfield_field_formatter_info() {
+  return WorkflowDefaultWidget::settings();
+}
+
+/**
+ * Implements hook_field_formatter_view().
+ *
+ * Shows current State + Widget on a Node View page or a Workflow History tab.
+ */
+function workflowfield_field_formatter_view($entity_type, $entity, $field, $instance, $langcode = LANGUAGE_NONE, $items = array(), $display = array()) {
+  global $user; // @todo #2287057: OK?
+  // @todo: Perhaps global user is not always the correct user.
+  // E.g., on ScheduledTransition->execute()? But this function is mostly used in UI.
+
+  $field_name = isset($field['field_name']) ? $field['field_name'] : '';
+  $field_id = isset($field['id']) ? $field['id'] : 0;
+
+  $current_sid = workflow_node_current_state($entity, $entity_type, $field_name);
+  $current_state = workflow_state_load_single($current_sid);
+
+  $list_element = array();
+  if ($field_name) {
+    // First compose the current value with the normal formatter from list.module.
+    $list_element = workflow_state_formatter($entity_type, $entity, $field, $instance, $current_sid);
+  }
+  elseif (!empty($field['settings']['widget']['current_status'])) {
+    $list_element = workflow_state_formatter($entity_type, $entity, $field, $instance, $current_sid);
+  }
+
+  // Check permission, so that even with state change rights,
+  // the form can be suppressed from the node view (#1893724).
+  if (!user_access('show workflow state form')) {
+    return $list_element;
+  }
+  if ($entity_type == 'comment') {
+    // No Workflow form allowed on a comment display.
+    // (Also, this avoids a lot of error messages.)
+    return $list_element;
+  }
+  // Only build form if user has possible target state(s).
+  if (!$current_state->showWidget($entity_type, $entity, $field_name, $user, FALSE)) {
+    return $list_element;
+  }
+
+  // Add the form/widget to the formatter, and include the nid in the form id,
+  // to allow multiple forms per page (in listings, with hook_forms() ).
+  // Ultimately, this is a wrapper for WorkflowDefaultWidget.
+  $entity_id = entity_id($entity_type, $entity);
+  $form_id = implode('_', array('workflow_transition_form', $entity_type, $entity_id, $field_id));
+  $element = drupal_get_form($form_id, $field, $instance, $entity_type, $entity);
+
+  return $element;
+}

+ 16 - 0
sites/all/modules/contrib/admin/workflow/workflow_field/workflowfield.info

@@ -0,0 +1,16 @@
+name = Workflow Field
+description = Defines a Workflow field, widget and formatter. (Do not enable Workflow Field and Workflow Node together.)
+package = Workflow
+core = 7.x
+
+dependencies[] = entity
+dependencies[] = field
+dependencies[] = options
+dependencies[] = workflow
+
+; Information added by Drupal.org packaging script on 2017-01-15
+version = "7.x-2.9+10-dev"
+core = "7.x"
+project = "workflow"
+datestamp = "1484510888"
+

+ 37 - 0
sites/all/modules/contrib/admin/workflow/workflow_field/workflowfield.install

@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Installs, updates and uninstalls functions for the list module.
+ */
+
+/**
+ * Implements hook_field_schema().
+ *
+ * This schema is copied from workflow.install, $schema['workflow_node'] .
+ */
+function workflowfield_field_schema($field) {
+    $columns = array(
+      'value' => array(
+        'description' => 'The {workflow_states}.sid that this node is currently in.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'disp-width' => '10',
+      ),
+    );
+
+  return array(
+    'columns' => $columns,
+/*
+    // 'primary key' => array('nid'),
+    // 'indexes' => array(
+    // 'nid' => array('nid', 'sid'),
+    // ),
+ */
+    'indexes' => array(
+      'value' => array('value'),
+    ),
+  );
+}

+ 24 - 0
sites/all/modules/contrib/admin/workflow/workflow_field/workflowfield.module

@@ -0,0 +1,24 @@
+<?php
+/**
+ * @file
+ * Supports workflows made up of arbitrary states.
+ */
+
+// The name of the Workfow form and function, to be used in various callbacks.
+define('WORKFLOWFIELD_FORM', 'workflowfield_field_widget_form');
+
+require_once dirname(__FILE__) . '/workflowfield.field.inc';
+require_once dirname(__FILE__) . '/workflowfield.widget.inc';
+require_once dirname(__FILE__) . '/workflowfield.formatter.inc';
+
+/**
+ * Implements hook_help().
+ */
+function workflowfield_help($path, $arg) {
+  switch ($path) {
+    case 'admin/config/workflow/workflow':
+      return t('This page allows you to maintain Workflows. Once a workflow is
+        created, you can maintain your entity type and add a Field of type
+        \'Workflow\'.');
+  }
+}

+ 79 - 0
sites/all/modules/contrib/admin/workflow/workflow_field/workflowfield.widget.inc

@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @file
+ * Defines a Workflow Field, Widget and Formatter for Workflow.
+ *
+ * All hooks are wrapper functions for a D8-style WorkflowDefaultWidget object.
+ * The Widget is copied from options.module); the Formatter from list field.
+ */
+
+/**
+ * Implements hook_field_widget_info().
+ */
+function workflowfield_field_widget_info() {
+  return WorkflowDefaultWidget::settings();
+}
+
+/**
+ * Implements hook_field_widget_settings_form().
+ */
+function workflowfield_field_widget_settings_form($field, $instance) {
+  $form = array();
+  $form_state = array();
+
+  // The form element is created by a D8-like object.
+  $widget = new WorkflowDefaultWidget($field, $instance);
+  return $widget->settingsForm($form, $form_state, $has_data = 0);
+}
+
+/**
+ * Implements hook_field_widget_form().
+ *
+ * param $langcode
+ *   obsolete. Removed from formElement() parameter list.
+ *
+ * This is a wrapper function for the 'workflow form' Widget. $form is modified by reference.
+ */
+function workflowfield_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
+  if (!isset($element['#entity'])) {
+    // We are now on the 'Manage Fields | Edit' settings page, so do nothing.
+    return $element;
+  }
+  elseif (!isset($element['#entity_type'])) {
+    // Not used: On an Entity View page, the widget is shown via
+    // workflowfield_field_formatter_view() and workflow_transition_form().
+    return $element;
+  }
+  else {
+    // We are on an Entity add/edit page.
+    if (!empty($field['settings']['widget']['hide'])) {
+      // We want the widget to be hidden, when states are only to be
+      // changed via Workflow History tab.
+      // This setting is needed, because D7 core does't have a <hidden> widget.
+      // @todo D8: remove this, since hidden widgets are available.
+      return $element;
+    }
+    else {
+      // Show the widget.
+      $entity_type = $element['#entity_type'];
+      $entity = $element['#entity'];
+
+      if ($entity_type == 'comment') {
+        // On a comment form, get the referenced entity.
+        $entity_type = 'node'; // Comments are only possible on nodes.
+        $entity = $form['#node'];
+        // Make sure no action buttons are added.
+        $instance['widget']['settings']['submit_function'] = array();
+      }
+
+      // Build a new $element.
+      // Do not use drupal_get_form() or you'll have a form in a form.
+      $element = array();
+      $widget = new WorkflowTransitionForm($field, $instance, $entity_type, $entity);
+      $element = $widget->buildForm($element, $form_state);
+      return $element;
+    }
+  }
+  return $element;
+}

+ 2 - 0
sites/all/modules/contrib/admin/workflow/workflow_node/README.txt

@@ -0,0 +1,2 @@
+This submodule contains the code to maintain workflows on nodes, using the classic Workflow way.
+If a version of Workflow was installed, before 7.x-2.0, this module is enabled by default.

+ 75 - 0
sites/all/modules/contrib/admin/workflow/workflow_node/plugins/access/workflow_bundle.inc

@@ -0,0 +1,75 @@
+<?php
+/**
+ * @file
+ * Describes a CTools Access plugin.
+ *
+ * Used to select whether or not a panel (variant) is displayed based
+ * upon the current workflow and/or workflow state of the current node.
+ * @see https://drupal.org/node/2187731
+ */
+
+/**
+ * Defines the Plugin.
+ *
+ * Plugins are described by creating a $plugin array which will
+ * be used by the system that includes the file.
+ */
+$plugin = array(
+  'title' => t('Workflow: bundle'),
+  'description' => t('Controls access by workflow bundle'),
+  'callback' => 'workflow_bundle_ctools_access_check',
+  'default' => array('workflow_bundle' => 0),
+  'settings form' => 'workflow_bundle_ctools_settings',
+  'summary' => 'workflow_bundle_ctools_summary',
+  'required context' => new ctools_context_required(t('Node'), 'node'),
+);
+
+/**
+ * Custom callback defined by 'callback' in the $plugin array.
+ *
+ * Check for access.
+ */
+function workflow_bundle_ctools_access_check($conf, $context) {
+  // For some unknown reason $context may not be set. We just want to be sure.
+  if (empty($context) || empty($context->data) || empty($context->data->type)) {
+    return FALSE;
+  }
+
+  // If the node's content type is not part of the selected workflow access to
+  // the pane is denied.
+  $type = $context->data->type;
+  $workflow = workflow_get_workflow_type_map_by_type($type);
+  if (isset($workflow->wid) && $conf['workflow_bundle'] == $workflow->wid) {
+    return TRUE;
+  }
+
+  return FALSE;
+}
+
+/**
+ * Settings form for the 'workflow bundle' access plugin.
+ */
+function workflow_bundle_ctools_settings($form, &$form_state, $conf) {
+  $options = array();
+  $workflows = workflow_get_workflows();
+  foreach ($workflows as $value) {
+    $options[$value->wid] = $value->name;
+  }
+
+  $form['settings']['workflow_bundle'] = array(
+    '#title' => t('Select workflow'),
+    '#type' => 'select',
+    '#options' => $options,
+    '#description' => t('The pane will only be visible for nodes of content types that belong to this workflow'),
+    '#default_value' => $conf['workflow_bundle'],
+  );
+  return $form;
+}
+
+/**
+ * Provide a summary description based upon the workflow bundle.
+ */
+function workflow_bundle_ctools_summary($conf, $context) {
+  $workflow = workflow_get_workflows_by_wid($conf['workflow_bundle']);
+  return t('Nodes that have workflow "@workflow"', array('@workflow' => $workflow->name));
+}

+ 82 - 0
sites/all/modules/contrib/admin/workflow/workflow_node/plugins/access/workflow_state.inc

@@ -0,0 +1,82 @@
+<?php
+/**
+ * @file
+ * Describes a CTools Access plugin.
+ *
+ * Used to select whether or not a panel (variant) is displayed based
+ * upon the current workflow and/or workflow state of the current node.
+ * @see https://drupal.org/node/2187731
+ */
+
+/**
+ * Defines the Plugin.
+ *
+ * Plugins are described by creating a $plugin array which will
+ * be used by the system that includes the file.
+ */
+$plugin = array(
+  'title' => t('Workflow: state'),
+  'description' => t('Controls access by workflow bundle'),
+  'callback' => 'workflow_state_ctools_access_check',
+  'default' => array('workflow_state' => 0),
+  'settings form' => 'workflow_state_ctools_settings',
+  'summary' => 'workflow_state_ctools_summary',
+  'required context' => new ctools_context_required(t('Node'), 'node'),
+);
+
+/**
+ * Custom callback defined by 'callback' in the $plugin array.
+ *
+ * Check for access.
+ */
+function workflow_state_ctools_access_check($conf, $context) {
+  // For some unknown reason $context may not be set. We just want to be sure.
+  if (empty($context) || empty($context->data) || empty($context->data->workflow)) {
+    return FALSE;
+  }
+
+  // If the node's content type is not part of the selected workflow access to
+  // the pane is denied.
+  $workflow_state = $context->data->workflow;
+  if ($conf['workflow_state'] == $workflow_state) {
+    return TRUE;
+  }
+
+  return FALSE;
+}
+
+/**
+ * Settings form for the 'workflow state' access plugin.
+ */
+function workflow_state_ctools_settings($form, &$form_state, $conf) {
+  $options = array();
+  $workflows = workflow_get_workflows();
+  foreach ($workflows as $workflow) {
+    $options[$workflow->name] = array();
+    $states = workflow_get_workflow_states_by_wid($workflow->wid);
+    foreach ($states as $state) {
+      $options[$workflow->name][$state->sid] = $state->state;
+    }
+  }
+
+  $form['settings']['workflow_state'] = array(
+    '#title' => t('Select workflow state'),
+    '#type' => 'select',
+    '#options' => $options,
+    '#description' => t('The pane will only be visible for nodes that are in this workflow state.'),
+    '#default_value' => $conf['workflow_state'],
+  );
+  return $form;
+}
+
+/**
+ * Provide a summary description based upon the workflow state.
+ */
+function workflow_state_ctools_summary($conf, $context) {
+  $state = workflow_get_workflow_states_by_sid($conf['workflow_state']);
+  return t('Nodes that have the workflow state "@state" in workflow "@workflow"', array(
+      '@state' => $state->state,
+      '@workflow' => $state->name,
+    )
+  );
+}

+ 60 - 0
sites/all/modules/contrib/admin/workflow/workflow_node/plugins/content_types/workflow_node_form.inc

@@ -0,0 +1,60 @@
+<?php
+/**
+ * @file
+ * Describes Workflow content pane for Panels.
+ */
+
+/**
+ * Defines the Plugin.
+ *
+ * Plugins are described by creating a $plugin array which will be used
+ * by the system that includes this file.
+ */
+$plugin = array(
+  'single' => TRUE,
+  'icon' => 'icon_node_form.png',
+  'title' => t('Workflow'),
+  'description' => t('Workflow states.'),
+  'required context' => new ctools_context_required(t('Form'), 'node_form'),
+  'category' => t('Form'),
+  'render callback' => 'workflownode_node_form_workflow_content_type_render',
+);
+
+/**
+ * Implements pane render callback.
+ */
+function workflownode_node_form_workflow_content_type_render($subtype, $conf, $panel_args, &$context) {
+  $block = new stdClass();
+  $block->module = t('node_form');
+  $block->title = t('Workflow');
+  $block->delta = 'workflow';
+
+  if (isset($context->form)) {
+    if (isset($context->form['workflow'])) {
+      $block->content['workflow'] = $context->form['workflow'];
+      // Set access to false on the original rather than removing so that
+      // vertical tabs doesn't clone it. I think this is due to references.
+      unset($context->form['workflow']);
+    }
+  }
+  else {
+    $block->content = t('Workflow.');
+  }
+
+  return $block;
+}
+
+/**
+ * Returns the administrative title for a type.
+ */
+function workflownode_node_form_workflow_content_type_admin_title($subtype, $conf, $context) {
+  return t('"@s" node form workflow', array('@s' => $context->identifier));
+}
+
+/**
+ * Implements pane edit callback.
+ */
+function workflownode_node_form_workflow_content_type_edit_form($form, &$form_state) {
+  // Provide a blank form so we have a place to have context setting.
+  return $form;
+}

+ 13 - 0
sites/all/modules/contrib/admin/workflow/workflow_node/workflownode.info

@@ -0,0 +1,13 @@
+name = Workflow Node
+description = Adds a Workflow to nodes. (Use only if you come from a version below 7.x-2.x. If not, use 'Workflow Field' instead).
+package = Workflow
+core = 7.x
+
+dependencies[] = workflow
+
+; Information added by Drupal.org packaging script on 2017-01-15
+version = "7.x-2.9+10-dev"
+core = "7.x"
+project = "workflow"
+datestamp = "1484510888"
+

+ 6 - 0
sites/all/modules/contrib/admin/workflow/workflow_node/workflownode.install

@@ -0,0 +1,6 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the Workflow node module.
+ */

+ 358 - 0
sites/all/modules/contrib/admin/workflow/workflow_node/workflownode.module

@@ -0,0 +1,358 @@
+<?php
+
+/**
+ * @file
+ * Hooks and functions for the 'conventional' (version D5/D6/D7.1) Workflow Node, remnants of nodeapi.
+ */
+
+// Includes the hooks for the 'conventional' (version D5/D6/D7.1) Node API.
+require_once(dirname(__DIR__)) . '/workflow.node.type_map.inc';
+require_once(dirname(__DIR__)) . '/workflow.deprecated.inc';
+
+/**
+ * Implements hook_ctools_plugin_directory().
+ *
+ * This lets ctools know to scan my module for a content_type plugin file
+ * Detailed docks in ctools/ctools.api.php
+ *
+ * This is used to integrate WorkflowNode with Panels. See d.o. #1511694, #2187731
+ */
+function workflownode_ctools_plugin_directory($owner, $plugin_type) {
+  if ($owner == 'ctools' && !empty($plugin_type)) {
+    return "plugins/$plugin_type";
+  }
+}
+
+/**
+ * Theme the current workflow state as top line of History tab.
+ */
+function theme_workflow_current_state($variables) {
+  $state = $variables['state'];
+  return '<div class="workflow-current-state '
+    . 'workflow-current-sid-' . intval($variables['sid']) . ' '
+    . drupal_html_class($state)
+    . '">'
+    . t('Current state: <span class="state">@state</span>', array('@state' => $state))
+    . '</div>';
+}
+
+/**
+ * Implements hook_node_load().
+ */
+function workflownode_node_load($nodes, $types) {
+  // Get which types have workflows associated with them.
+  $workflow_types = workflow_get_workflow_type_map();
+
+  // Leave early if nodes do not have workflow.
+  if (!$workflow_types || !array_intersect_key($workflow_types, array_flip($types))) {
+    return;
+  }
+
+  // Read the current state for all nodes.
+  $states = workflow_get_workflow_node_by_nid(array_keys($nodes));
+
+  foreach ($nodes as $nid => $node) {
+    // If it's not a workflow type, quit immediately.
+    if (!array_key_exists($node->type, $workflow_types)) {
+      continue;
+    }
+    // Nodes that existed before the workflow was defined may not have a state.
+    $workflow_node = isset($states[$nid]) ? $states[$nid] : NULL;
+    if (!$workflow_node) {
+      if ($workflow = workflow_get_workflows_by_type($node->type, 'node')) {
+        $node->workflow = $workflow->getCreationSid();
+        $node->workflow_stamp = $node->created;
+      }
+      else {
+        // Is this possible?
+      }
+    }
+    else {
+      $node->workflow = $workflow_node->sid;
+      $node->workflow_stamp = $workflow_node->stamp;
+    }
+  }
+
+  // As of workflow 7.x-2.x, the scheduled transition is not loaded. See issue #2138591.
+}
+
+/**
+ * Implements hook_node_view().
+ */
+function workflownode_node_view($node, $view_mode, $langcode) {
+  global $user;
+
+  $form = array();
+  $entity_type = 'node'; // This hook is only for nodes.
+  $entity_id = $node->nid;
+  $field_name = ''; // This hook is only for workflow_node.
+  $entity_bundle = $node->type;
+
+  // Skip if there are no Node API workflows.
+  if (!workflow_get_workflow_type_map_by_type($entity_bundle)) {
+    return;
+  }
+  // Check permission, so that even with state change rights,
+  // the form can be suppressed from the node view (#1893724).
+  if (!user_access('show workflow state form', $user)) {
+    return;
+  }
+
+  // Get the current sid. $field_name is updated with relevant value.
+  $current_sid = workflow_node_current_state($node, $entity_type, $field_name);
+  $current_state = workflow_state_load_single($current_sid);
+
+  // Show current state at the top of the node display.
+  $field = $instance = array();
+  $node->content['workflow_current_state'] = workflow_state_formatter($entity_type, $node, $field, $instance, $current_sid);
+  $node->content['workflow_current_state']['#weight'] = -99;
+
+  if ($current_state && $current_state->showWidget('node', $node, '', $user, FALSE)) {
+    $workflow = $current_state->getWorkflow();
+    // Show the current state and the Workflow form to allow state changing.
+    // N.B. This part is replicated in hook_node_view, workflow_tab_page, workflow_vbo.
+    if ($workflow) {
+      $field = _workflow_info_field($field_name, $workflow);
+      $field_id = $field['id'];
+      $instance = field_info_instance($entity_type, $field_name, $entity_bundle);
+      // If no instance is found, restore the array.
+      if (!is_array($instance)) {
+        $instance = array();
+      }
+      if (!$field['id']) {
+        // This is a Workflow Node workflow. Set widget options as in v7.x-1.2
+        $field['settings']['widget']['comment'] = isset($workflow->options['comment_log_node']) ? $workflow->options['comment_log_node'] : 1; // vs. ['comment_log_tab'];
+        $field['settings']['widget']['current_status'] = FALSE;
+      }
+    }
+
+    // By including the nid in the form id. For backwards compatiblity, do not add entity_type, field_id.
+    $form_id = implode('_', array('workflow_transition_form', $entity_id));
+    $form += drupal_get_form($form_id, $field, $instance, $entity_type, $node);
+    $form['#weight'] = 99;
+
+    $node->content['workflow'] = $form;
+  }
+}
+
+/**
+ * Implements hook_node_insert().
+ *
+ * This is executed after saving data to the database.
+ * We cannot use hook_node_presave, because workflow_execute_transition() needs the nid.
+ */
+function workflownode_node_insert($node) {
+  global $user;
+
+  if (!isset($node->workflow_field)) {
+    // Initializing the state of the node in case no widget on Node form.
+    if ($workflow = workflow_get_workflows_by_type($node->type, 'node')) {
+      $comment = t('Set to initial state.');
+      $force = TRUE;
+      $creation_sid = $workflow->getCreationSid();
+
+      // Get the initial state for this node.
+      // Due to permissions, it might be different for each user.
+      $new_sid = $workflow->getFirstSid('node', $node, null, $user, $force);
+
+      $transition = new WorkflowTransition();
+      $transition->setValues('node', $node, null, $creation_sid, $new_sid, $user->uid, REQUEST_TIME, $comment);
+
+      // Force it to transition to the first state and get a history record.
+      workflow_execute_transition('node', $node, null, $transition, $force);
+    }
+  }
+  return workflownode_node_update($node);
+}
+
+/**
+ * Implements hook_node_update().
+ */
+function workflownode_node_update($node) {
+  if (!isset($node->workflow_field)) {
+    // For this type_map, user did not want a form here.
+    return;
+  }
+
+  $form = array();
+
+  // Retrieve the data from the form.
+  // Add form values (field, instance, entity_type, entity), then form input.
+  $form_state = array();
+  $form_state['values']['workflow_field'] = $node->workflow_field;
+  $form_state['values']['workflow_instance'] = $node->workflow_instance;
+  $form_state['values']['workflow_entity_type'] = 'node';
+  // Careful: take the fresh node here, not the one that is in the form.
+  $form_state['values']['workflow_entity'] = $node;
+  // For some reason, the Workflow Form does not return the form in a 'workflow' array.
+  // @todo: correct this (use '#tree => TRUE'), or filter on 'workflow' elements.
+  $form_state['input'] = (array) $node;
+
+  workflow_transition_form_submit($form, $form_state);
+}
+
+/**
+ * Implements hook_node_delete().
+ */
+function workflownode_node_delete($node) {
+  if (!empty($node->workflow)) {
+    // Call field_info_field().
+    // Generates pseudo data for workflow_node to re-use Field API.
+    $field = _workflow_info_field($field_name = '', $workflow = NULL);
+    $instance = array();
+    $items[0]['value'] = $node->workflow;
+
+    $workflow_field = new WorkflowItem($field, $instance, 'node', $node);
+    $workflow_field->delete($items);
+  }
+}
+
+/**
+ * Implements hook_comment_insert().
+ */
+function workflownode_comment_insert($comment) {
+  workflownode_comment_update($comment);
+}
+
+/**
+ * Implements hook_comment_update().
+ */
+function workflownode_comment_update($comment) {
+  // Retrieve the data from the form.
+  if (!isset($comment->workflow_field)) {
+    // For this type_map, user did not want a form here.
+    return;
+  }
+
+  $form = array();
+
+  // Retrieve the data from the form.
+  // Add form values (field, instance, entity_type, entity), then form input.
+  $form_state = array();
+  $form_state['values']['workflow_field'] = $comment->workflow_field;
+  $form_state['values']['workflow_instance'] = $comment->workflow_instance;
+  $form_state['values']['workflow_entity_type'] = $comment->workflow_entity_type;
+  $form_state['values']['workflow_entity'] = $comment->workflow_entity;
+  // For some reason, the Workflow Form does not return the form in a 'workflow' array.
+  // @todo: correct this, or filter on 'workflow' elements.
+  $form_state['input'] = (array) $comment;
+
+  workflow_transition_form_submit($form, $form_state);
+}
+
+/**
+ * Implements hook_field_extra_fields().
+ */
+function workflownode_field_extra_fields() {
+  $extra = array();
+
+  // Get all workflows by content types.
+  $types = array_filter(workflow_get_workflow_type_map());
+
+  // Add the extra fields to each content type that has a workflow.
+  foreach ($types as $type => $wid) {
+    $extra['node'][$type] = array(
+      'form' => array(
+        'workflow' => array(
+          'label' => t('Workflow'),
+          'description' => t('Workflow module form'),
+          'weight' => 99,    // Default to bottom.
+        ),
+      ),
+      'display' => array(
+        'workflow_current_state' => array(
+          'label' => t('Workflow: Current State'),
+          'description' => t('Current workflow state'),
+          'weight' => -99,   // Default to top.
+        ),
+        'workflow' => array(
+          'label' => t('Workflow: State Change Form'),
+          'description' => t('The form for controlling workflow state changes.'),
+          'weight' => 99,    // Default to bottom.
+        ),
+      ),
+    );
+  }
+
+  return $extra;
+}
+
+/**
+ * Implements hook_form_BASE_FORM_ID_alter().
+ *
+ * Shows the form only on Node Edit forms.
+ */
+function workflownode_form_node_form_alter(&$form, &$form_state, $form_id) {
+  _workflownode_form_alter($form, $form_state, $form_id);
+}
+
+/**
+ * Implements hook_form_BASE_FORM_ID_alter().
+ *
+ * Shows the form only on Comment forms.
+ */
+function workflownode_form_comment_form_alter(&$form, &$form_state, $form_id) {
+  _workflownode_form_alter($form, $form_state, $form_id);
+}
+
+/**
+ * Helper function to implement hook_form_alter().
+ *
+ * Is now a subfunction of workflow_form_BASE_FORM_ID_alter().
+ * This is more performant (compared to 7.x-1.2 and before), since it is called
+ * only on form with correct BASE_FORM_ID.
+ *
+ * @see http://bryanbraun.com/2013/08/17/using-hook-form-base-form-id-alter
+ */
+function _workflownode_form_alter(&$form, &$form_state, $form_id) {
+  // Set node to #node if available or load from nid value.
+  $node = isset($form['#node']) ? $form['#node'] : node_load($form['nid']['#value']);
+  $bundle = $node->type;
+  $entity = $node;
+  $entity_type = 'node';
+  $field_name = '';
+
+  // Skip if there are no Node API workflows.
+  if (!workflow_get_workflow_type_map_by_type($bundle)) {
+    return;
+  }
+  if ($workflow = workflow_get_workflows_by_type($bundle, 'node')) {
+    $workflow_entities = variable_get('workflow_' . $bundle, array());
+    // Abort if the entity type of the form is not in the list that the user
+    // wants to display the workflow form on.
+    if (!in_array($form['#entity_type'], $workflow_entities)) {
+      return;
+    }
+/*
+      $form['#wf'] = $workflow;
+      $form['workflow'] = array(
+        '#type' => 'fieldset',
+        '#title' => check_plain($label),
+        '#collapsible' => TRUE,
+        '#collapsed' => FALSE,
+        '#weight' => 10,
+      );
+ */
+
+    // Emulate a Field API interface.
+    // Show the current state and the Workflow form to allow state changing.
+    // N.B. This part is replicated in hook_node_view, workflow_tab_page, etc.
+    if ($workflow) {
+      $field = _workflow_info_field($field_name, $workflow);
+      $field_name = $field['field_name'];
+      $field_id = $field['id'];
+      $instance = field_info_instance($entity_type, $field_name, $bundle);
+      if (!$field_id) {
+        // This is a Workflow Node workflow. Set widget options as in v7.x-1.2
+        $field['settings']['widget']['comment'] = isset($workflow->options['comment_log_node']) ? $workflow->options['comment_log_node'] : 1; // vs. ['comment_log_tab'];
+        $field['settings']['widget']['current_status'] = TRUE;
+      }
+    }
+
+    // Do not include the default 'Update Workflow' button, since we are already in an Edit form.
+    $instance['widget']['settings']['submit_function'] = '';
+
+    // Include the 'workflow form'. $form is modified by reference.
+    $form = workflow_transition_form($form, $form_state, $field, $instance, $entity_type, $entity);
+  }
+}

+ 247 - 0
sites/all/modules/contrib/admin/workflow/workflow_node/workflownode.tokens.inc

@@ -0,0 +1,247 @@
+<?php
+
+/**
+ * @file
+ * Tokens hooks for Workflow Node module.
+ *
+ * These tokens are deprecated in favour of 'chained' tokens in main module.
+ */
+
+/**
+ * Implements hook_token_info().
+ */
+function workflownode_token_info() {
+  $types['workflow'] = array(
+    'name' => t('Workflow'),
+    'description' => t('Tokens related to workflows.'),
+    'needs-data' => 'node',
+  );
+
+  // 'plain_tokens' indicates a 'chained_tokens' counterpart.
+  $plain_tokens = array();
+  $plain_tokens['workflow-name'] = array(
+    'name' => t('Workflow name'),
+    'description' => t('Name of workflow applied to this node.'),
+  );
+  $plain_tokens['workflow-current-state-name'] = array(
+    'name' => t('Current state name'),
+    'description' => t('Current state of content.'),
+  );
+  $plain_tokens['workflow-old-state-name'] = array(
+    'name' => t('Old state name'),
+    'description' => t('Old state of content.'),
+  );
+  $plain_tokens['workflow-current-state-updating-user-name'] = array(
+    'name' => t('Username of last state changer'),
+    'description' => t('Username of last state changer.'),
+  );
+  $plain_tokens['workflow-current-state-updating-user-link'] = array(
+    'name' => t('Username link of last state changer'),
+    'description' => t('Themed Username of last state changer.'),
+  );
+  $plain_tokens['workflow-current-state-updating-user-uid'] = array(
+    'name' => t('Uid of last state changer'),
+    'description' => t('uid of last state changer.'),
+  );
+  $plain_tokens['workflow-current-state-updating-user-mail'] = array(
+    'name' => t('Email of last state changer'),
+    'description' => t('email of last state changer.'),
+  );
+  $plain_tokens['workflow-current-state-updating-user-mail'] = array(
+    'name' => t('Email link of last state changer'),
+    'description' => t('email link of last state changer.'),
+  );
+  $plain_tokens['workflow-current-state-log-entry'] = array(
+    'name' => t('Last workflow comment log'),
+    'description' => t('Last workflow comment log.'),
+  );
+  $plain_tokens['workflow-current-state-date-iso'] = array(
+    'name' => t('Current state date (ISO)'),
+    'description' => t('Date of last state change (ISO).'),
+  );
+  $plain_tokens['workflow-current-state-date-tstamp'] = array(
+    'name' => t('Current state date (timestamp)'),
+    'description' => t('Date of last state change (timestamp).'),
+  );
+  $plain_tokens['workflow-current-state-date-formatted'] = array(
+    'name' => t('Current state date (formatted by site default)'),
+    'description' => t('Date of last state change (formatted by site default).'),
+  );
+
+  // Add support for custom date formats. (see token.tokens.inc)
+  // @todo Remove when http://drupal.org/node/1173706 is fixed.
+  $date_format_types = system_get_date_types();
+  foreach ($date_format_types as $date_type => $date_info) {
+    $plain_tokens['workflow-current-state-date-' . $date_type] = array(
+      'name' => t('Current state date (using @format format)', array('@format' => $date_info['title'])),
+      'description' => t("A date in '@type' format. (%date)", array('@type' => $date_type, '%date' => format_date(REQUEST_TIME, $date_type))),
+      'module' => 'workflow',
+    );
+  }
+
+  return array(
+    'types' => $types,
+    'tokens' => array(
+      'workflow' => $plain_tokens,
+      'node' => $plain_tokens,
+    ),
+  );
+}
+
+/**
+ * Implements hook_tokens().
+ *
+ * This is still buggy for Workflow Field:
+ * - we do not know upfront which field to read.
+ * - some evaluations may not be correct.
+ */
+function workflownode_tokens($type, $tokens, array $data = array(), array $options = array()) {
+  $replacements = array();
+  $sanitize = !empty($options['sanitize']);
+  $langcode = !empty($options['language']->language) ? $options['language']->language : LANGUAGE_NONE;
+  $date = REQUEST_TIME;
+
+  if (($type == 'node' || $type == 'workflow') && !empty($data['node'])  && !empty($data['node']->nid)) {
+    $node = $data['node'];
+    $entity_id = $node->nid;
+    $entity_type = 'node'; // @todo: support other entity types in workflow_tokens().
+    $entity_bundle = $node->type;
+    // We do not know which field we want to use. Just pick one.
+    $field_name = ''; // For workflow_node, multiple workflow per node_type are not supported.
+
+    if ($workflow = workflow_get_workflows_by_type($entity_bundle, $entity_type)) {
+      // Get a list of all possible states for returning state names.
+      $states = workflow_state_load_multiple();
+
+      $last_history = workflow_transition_load_single($entity_type, $entity_id, '');
+
+      if (isset($node->workflow) && !isset($node->workflow_stamp)) {
+        // Workflow Node mode.
+        // The node is being submitted but the form data has not been saved to the database yet,
+        // so we set the token values from the workflow form fields.
+        $uid = $node->uid;
+        $account = user_load($uid);
+        $new_sid = $node->workflow;
+        $old_sid = isset($last_history->old_sid) ? $last_history->old_sid : $workflow->getCreationSid();
+        $user_name = ($uid && isset($node->name)) ? $node->name : variable_get('anonymous', 'Anonymous');
+        $mail = ($uid && isset($node->user_mail)) ? $node->user_mail : '';
+        $comment = isset($node->workflow_comment) ? $node->workflow_comment : '';
+      }
+      else {
+        // Workflow Node/Workflow Field mode.
+        if (empty($last_history)) {
+          // If the node has no workflow history, the node is being inserted
+          // and will soon be transitioned to the first valid state.
+          $uid = $node->uid;
+          $account = user_load($uid);
+          $old_sid = $workflow->getCreationSid();
+          $new_sid = $workflow->getFirstSid($entity_type, $node, '', $account, FALSE);
+          if (!$new_sid) {
+            // Some users may not be allowed to cause transitions, so there
+            // will be no choices. What to choose?
+            $new_sid = workflow_node_current_state($node, $entity_type, $field_name);
+          }
+          $user_name = ($uid && isset($node->name)) ? $node->name : variable_get('anonymous', 'Anonymous');
+          $mail = ($uid && isset($node->user_mail)) ? $node->user_mail : '';
+          $comment = isset($node->workflow_comment) ? $node->workflow_comment : '';
+        }
+        else {
+          // We now know the correct field_name.
+          $field_name = $last_history->field_name; // @todo: support multiple Workflow fields per entity in workflow_tokens().
+
+          // Sometimes there is no workflow set (edit?).
+          // Is a transition in progress?
+          $current_sid = workflow_node_current_state($node, $entity_type, $field_name);
+          if ($current_sid != $last_history->new_sid) {
+            $new_sid = $current_sid;
+            $old_sid = $last_history->new_sid;
+            $date = REQUEST_TIME; // $node->workflow_stamp;
+            $uid = $node->uid;
+          }
+          else {
+            // Not a transition.
+            $new_sid = $last_history->new_sid;
+            $old_sid = $last_history->old_sid;
+            $date = $last_history->stamp;
+            $uid = $last_history->uid;
+          }
+
+          $account = user_load($uid);
+          // Default to the most recent transition data in the workflow history table.
+          $user_name = $account->uid ? $account->name : variable_get('anonymous', 'Anonymous');
+          $mail = $account->uid ? $account->mail : '';
+          $comment = $last_history->comment;
+        }
+      }
+
+      foreach ($tokens as $name => $original) {
+        switch ($name) {
+          case 'workflow-name':
+            $replacements[$original] = $sanitize ? check_plain($workflow->label()) : $workflow->label();
+            break;
+
+          case 'workflow-current-state-name':
+            $replacements[$original] = $sanitize ? check_plain($states[$new_sid]->getName()) : $states[$new_sid]->getName();
+            break;
+
+          case 'workflow-old-state-name':
+            $old_state_name = (!empty($states[$old_sid])) ? $states[$old_sid]->getName() : 'Deleted state';
+            $replacements[$original] = $sanitize ? check_plain($old_state_name) : $old_state_name;
+            break;
+
+          case 'workflow-current-state-updating-user-name':
+            $name = format_username($account);
+            $replacements[$original] = $sanitize ? check_plain($name) : $name;
+            break;
+
+          case 'workflow-current-state-updating-user-link':
+            $replacements[$original] = theme('username', array('account' => $account));
+            break;
+
+          case 'workflow-current-state-updating-user-uid':
+            // User IDs are integers only and do not need sanitization.
+            $replacements[$original] = $uid;
+            break;
+
+          case 'workflow-current-state-updating-user-mail':
+            $replacements[$original] = $sanitize ? check_plain($account->mail) : $account->mail;
+            break;
+
+          case 'workflow-current-state-updating-user-mailto':
+            $replacements[$original] = '<a href="mailto:' . check_plain($account->mail) . '">' . check_plain($user_name) . '</a>';
+            break;
+
+          case 'workflow-current-state-log-entry':
+            $replacements[$original] = $sanitize ? check_markup($comment, filter_default_format(), $langcode) : $comment;
+            break;
+
+          case 'workflow-current-state-date-iso':
+            $replacements[$original] = format_date($date, 'custom', 'c', NULL, $langcode);
+            break;
+
+          case 'workflow-current-state-date-tstamp':
+            $replacements[$original] = $sanitize ? check_plain($date) : $date;
+            break;
+
+          case 'workflow-current-state-date-formatted':
+            $replacements[$original] = format_date($date, 'medium', '', NULL, $langcode);
+            break;
+
+          default:
+            // Add support for custom date formats. (see token.tokens.inc)
+            // @todo Remove when http://drupal.org/node/1173706 is fixed.
+            // @todo It seems the custom date formats work normally in D7.22 with chained tokens.
+            $date_format_types = system_get_date_types();
+            foreach ($date_format_types as $date_type => $date_info) {
+              if ($name == 'workflow-current-state-date-' . $date_type) {
+                $replacements[$original] = format_date($date, $date_type, '', NULL, $langcode);
+              }
+            }
+            break;
+        }
+      }
+    }
+  }
+
+  return $replacements;
+}

+ 38 - 0
sites/all/modules/contrib/admin/workflow/workflow_node/workflownode.workflow.inc

@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Hooks provided by the workflow module.
+ */
+
+/**
+ * Implements hook_workflow().
+ */
+function workflownode_workflow($op, $id, $new_sid, $entity, $force, $entity_type = '', $field_name = '', $transition = NULL) {
+  switch ($op) {
+    case 'transition permitted':
+      return TRUE;
+
+    case 'transition pre':
+      break;
+
+    case 'transition post':
+      break;
+
+    case 'transition delete':
+      // A transition is deleted. Only the first parameter is used.
+      // $tid = $id;
+      break;
+
+    case 'state delete':
+      // A state is deleted. Only the first parameter is used.
+      // $current_sid = $id;
+      break;
+
+    case 'workflow delete':
+      // A workflow is deleted. Only the first parameter is used.
+      $wid = $id;
+      workflow_delete_workflow_type_map_by_wid($wid);
+      break;
+  }
+}

+ 236 - 0
sites/all/modules/contrib/admin/workflow/workflow_notify/workflow_notify.admin.inc

@@ -0,0 +1,236 @@
+<?php
+/**
+ * @file
+ * Admin UI to Notify roles for Workflow state transitions.
+ */
+
+/**
+ * Settings form.
+ */
+function workflow_notify_settings_form($form, $form_state, $workflow) {
+  // If we don't have a workflow that goes with this, return to the admin pg.
+  if ($workflow) {
+    // Let's get a better page title.
+    drupal_set_title(t('@name Notifications', array('@name' => ucwords($workflow->name))));
+
+    // Let's add some breadcrumbs.
+    workflow_admin_ui_breadcrumbs($workflow);
+
+    $noyes = array(t('No'), t('Yes'));
+
+    $form['wid'] = array('#type' => 'value', '#value' => $workflow->wid);
+    $form['#workflow'] = $workflow;
+
+    // Get all the states for this workflow, except the (creation) state.
+//    $states = workflow_get_workflow_states_by_wid($workflow->wid,
+//      array('status' => 1, 'sysid' => 0));
+    $states = array();
+    foreach ($workflow->states as $sid => $state) {
+      if (!$state->sysid) {
+        $states[$sid] = $state;
+      }
+    }
+    if (!$states) {
+      $form['error'] = array(
+        '#type' => 'markup',
+        '#value' => t('There are no states defined for this workflow.'),
+      );
+      return $form;
+    }
+
+    $form['#states'] = $states;
+
+    // Get all roles, except anonymous.
+    $roles = user_roles(TRUE);
+    $roles = array('-1' => t('Author')) + user_roles(TRUE);
+
+    // The module_invoke_all function does not allow passing by reference.
+    $args = array('roles' => $roles);
+    foreach (module_implements('workflow_notify') as $module) {
+      $function = $module . '_workflow_notify';
+      // Call the $op='roles' hooks so the list may be modified.
+      $function('roles', $args);
+    }
+    // Get the modified list.
+    $roles = $args['roles'];
+
+    // Setting for "from" address.
+    $form['from_address'] = array(
+      '#title' => t('Set "From" address to'),
+      '#type' => 'radios',
+      '#options' => array(
+        'site' => t('Site email address'),
+        'changer' => t('Current user'),
+        ),
+      '#default_value' => variable_get('workflow_notify_from_address_' . $workflow->wid, 'site'),
+      '#attributes' => array('class' => array('container-inline')),
+      '#description' => t('Determines whether to use the site address or the address of the person
+        causing the state change.'),
+      );
+
+    // Get the list of input formats.
+    $formats = array();
+    foreach (filter_formats() as $fid => $filter) {
+      $formats[$fid] = $filter->name;
+    }
+
+    $form['filter'] = array(
+      '#title' => t('Filter format to use on message'),
+      '#type' => 'radios',
+      '#options' => $formats,
+      '#default_value' => variable_get('workflow_notify_filter_format_' . $workflow->wid, 'filtered_html'),
+      '#attributes' => array('class' => array('container-inline')),
+      '#description' => t('The message body will be processed using this filter format
+        in order to ensure security.'),
+      );
+
+    $form['tokens'] = array(
+      '#theme' => 'token_tree_link',
+      '#token_types' => array('node', 'workflow'),
+      );
+
+    // Column names for theme function.
+    $columns = array(
+      'state' => t('State'),
+      'roles' => t('Roles to notify'),
+      );
+
+    $args = array('columns' => $columns);
+    // The module_invoke_all function does not allow passing by reference.
+    foreach (module_implements('workflow_notify') as $module) {
+      $function = $module . '_workflow_notify';
+      $function('columns', $args);
+    }
+    $form['#columns'] = $args['columns'];
+
+    // We always want subject and body last.
+    $form['#columns']['subject'] = t('Subject');
+    $form['#columns']['body'] = t('Body');
+
+    $notify = variable_get('workflow_notify_roles', array());
+
+    foreach ($states as $sid => $state) {
+      // Allow modules to insert state operations.
+      $state_links = module_invoke_all('workflow_operations', 'state', $workflow, $state);
+
+      $form['states'][$sid] = array(
+        'state' => array(
+          '#type' => 'markup',
+          '#markup' => filter_xss($state->state),
+          ),
+
+        'roles' => array(
+          '#type' => 'checkboxes',
+          '#options' => $roles,
+          '#default_value' => (isset($notify[$sid]) ? $notify[$sid] : array()),
+          '#attributes' => array('class' => array('state-roles')),
+          ),
+
+        'subject' => array(
+          '#type' => 'textarea',
+          '#rows' => 5,
+          '#default_value' => variable_get('workflow_notify_subject_' . $sid,
+            '[node:title] is now "[node:field_workflow_state:new-state:label]"'),
+          '#attributes' => array('class' => array('subject')),
+          ),
+
+        'body' => array(
+          '#type' => 'textarea',
+          '#rows' => 5,
+          '#default_value' => variable_get('workflow_notify_body_' . $sid,
+            '<a href="[node:url:absolute]">[node:title]</a> is now "[node:field_workflow_state:new-state:label]".'),
+          '#attributes' => array('class' => array('body')),
+          ),
+        );
+    }
+
+    $form['#tree'] = TRUE;
+    $form['submit'] = array('#type' => 'submit', '#value' => 'Save notifications settings');
+
+    return $form;
+  }
+  else {
+    drupal_goto('admin/config/workflow/workflow');
+  }
+}
+
+/**
+ * Theme the settings form.
+ */
+function theme_workflow_notify_settings_form($variables) {
+  $form = $variables['form'];
+  $output = '';
+  $table_id = 'workflow-notify-settings';
+
+  $output .= drupal_render($form['from_address']);
+  $output .= drupal_render($form['filter']);
+  $output .= drupal_render($form['tokens']);
+
+  $table = array(
+    'rows' => array(),
+    'header' => array(),    // To be filled in.
+    'attributes' => array('id' => $table_id, 'style' => 'width: 100%; margin-top: 1em;'),
+    );
+
+  $columns = $form['#columns'];
+  $colspan = count($columns);
+
+  foreach ($columns as $field => $title) {
+    $table['header'][] = $title;
+  }
+
+  // Iterate over each element in our $form['states'] array
+  foreach (element_children($form['states']) as $id) {
+    // We are now ready to add each element of our $form data to the rows
+    // array, so that they end up as individual table cells when rendered
+    // in the final table.
+    $row = array();
+
+    foreach ($columns as $field => $title) {
+      $row[] = array(
+        'data' => drupal_render($form['states'][$id][$field]),
+        'class' => array(drupal_html_class($field)),
+        );
+    }
+
+    // Put the data row into the table.
+    $table['rows'][] = array('data' => $row);
+  }
+
+  $output .= theme('table', $table);
+
+  $output .= drupal_render($form['explain']);
+
+  // And then render any remaining form elements (such as our submit button)
+  $output .= drupal_render_children($form);
+
+  return $output;
+}
+
+function workflow_notify_settings_form_submit($form, $form_state) {
+  $roles = variable_get('workflow_notify_roles', array());
+
+  $workflow = $form['#workflow'];
+
+  variable_set('workflow_notify_from_address_' . $workflow->wid , $form_state['values']['from_address']);
+  variable_set('workflow_notify_filter_format_' . $workflow->wid , $form_state['values']['filter']);
+
+  foreach ($form_state['values']['states'] as $sid => $values) {
+    $selected = array_filter($values['roles']);
+    // Are there any roles selected?
+    if ($selected) {
+      $roles[$sid] = $selected;
+    }
+    else {
+      // No, so make sure this state is gone.
+      unset($roles[$sid]);
+    }
+
+    variable_set("workflow_notify_subject_$sid", $values['subject']);
+    variable_set("workflow_notify_body_$sid", $values['body']);
+  }
+
+  variable_set('workflow_notify_roles', $roles);
+
+  drupal_set_message(t('The notification settings have been saved.'));
+}

+ 15 - 0
sites/all/modules/contrib/admin/workflow/workflow_notify/workflow_notify.info

@@ -0,0 +1,15 @@
+name = Workflow Notify
+description = Notify roles of Workflow transitions.
+dependencies[] = workflow
+configure = admin/config/workflow/workflow
+core = 7.x
+package = Workflow
+
+dependencies[] = token
+
+; Information added by Drupal.org packaging script on 2017-01-15
+version = "7.x-2.9+10-dev"
+core = "7.x"
+project = "workflow"
+datestamp = "1484510888"
+

+ 297 - 0
sites/all/modules/contrib/admin/workflow/workflow_notify/workflow_notify.module

@@ -0,0 +1,297 @@
+<?php
+/**
+ * @file
+ * Notify roles for Workflow state transitions.
+ */
+
+/**
+ * Implements hook_permission().
+ */
+function workflow_notify_permission() {
+  return array(
+    'workflow notify' => array(
+      'title' => t('Receive workflow notifications'),
+      'description' => t('The user may be notified of a workflow state change.'),
+    ),
+  );
+}
+
+/**
+ * Implements hook_help().
+ */
+function workflow_notify_help($path, $arg) {
+  switch ($path) {
+    case 'admin/config/workflow/workflow/notify/%':
+      return '<p>' . t('The selected roles will be notified of each state change selected.') . '</p>';
+  }
+}
+
+/**
+ * Implements hook_menu().
+ */
+function workflow_notify_menu() {
+  $items = array();
+
+  $items['admin/config/workflow/workflow/notify/%workflow'] = array(
+    'title' => 'Notifications',
+    'access arguments' => array('administer workflow'),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('workflow_notify_settings_form', 5),
+    'type' => MENU_CALLBACK,
+    'file' => 'workflow_notify.admin.inc',
+  );
+
+  return $items;
+}
+
+/**
+ * Implements hook_theme().
+ */
+function workflow_notify_theme() {
+  return array(
+    'workflow_notify_settings_form' => array(
+      'render element' => 'form',
+      'file' => 'workflow_notify.admin.inc',
+    ),
+  );
+}
+
+/**
+ * Implements hook_hook_info().
+ */
+function workflow_notify_hook_info() {
+  $hooks['workflow_notify'] = array(
+    'group' => 'workflow',
+  );
+  return $hooks;
+}
+
+/**
+ * Implements hook_workflow_operations().
+ * Add an action link to this module.
+ */
+function workflow_notify_workflow_operations($op, $workflow = NULL) {
+  switch ($op) {
+    case 'workflow':
+      $wid = $workflow->getWorkflowId();
+
+      $actions = array(
+        'workflow_notify_settings' => array(
+          'title' => t('Notifications'),
+          'href' => "admin/config/workflow/workflow/notify/$wid",
+          'attributes' => array('alt' => t('Notify users about state changes.')),
+        ),
+      );
+
+      $actions['workflow_notify_settings']['attributes']['title'] =
+        $actions['workflow_notify_settings']['attributes']['alt'];
+
+      return $actions;
+  }
+}
+
+/**
+ * Implements hook_workflow().
+ * Calls drupal_mail via _workflow_notify() for Workflow Field;
+ *
+ * @param $op
+ *   The current workflow operation: 'transition permitted', 'transition pre', or 'transition post'.
+ * @param $old_state
+ *   The state ID of the current state.
+ * @param $new_state
+ *   The state ID of the new state.
+ * @param $node
+ *   The node whose workflow state is changing.
+ * @param $force
+ *   The caller indicated that the transition should be forced. (bool).
+ *   This is only available on the "pre" and "post" calls.
+ */
+function workflow_notify_workflow($op, $old_state, $new_state, $entity, $force, $entity_type = '', $field_name = '', $transition = NULL, $user = NULL) {
+  global $user;
+
+  switch ($op) {
+    // React to a transition after it's done.
+    case 'transition post':
+      // This is only called for Workfow Node. For Workflow Field, see workflow_entity_entity_update().
+      _workflow_notify($new_state, $entity, $entity_type, $field_name, $transition, $user);
+      return;
+  }
+}
+
+/**
+ * Implements hook_entity_update().
+ * Calls drupal_mail via _workflow_notify() for Workflow Field;
+ *
+ * @param $entity
+ * @param $entity_type
+ */
+function workflow_notify_entity_update($entity, $entity_type) {
+  if (isset($entity->workflow_transitions)) {
+    $new_state = workflow_node_current_state($entity, $entity_type, $field_name);
+    foreach ($entity->workflow_transitions as $field_name => &$transition) {
+      $new_state = workflow_node_current_state($entity, $entity_type, $field_name);
+      _workflow_notify($new_state, $entity, $entity_type, $field_name, $transition, $user = NULL);
+    }
+  }
+  return;
+}
+
+/**
+ * Implements hook_mail();
+ * Build email messages.
+ */
+function workflow_notify_mail($key, &$message, $params) {
+  switch ($key) {
+    case 'workflow_notify':
+      $filter = $params['filter'];
+      $message['send'] = TRUE;
+
+      $message['subject'] = filter_xss(token_replace($params['context']['subject'], $params['data'], $params));
+      $message['body'][] = check_markup(token_replace($params['context']['body'], $params['data'], $params), $filter);
+
+      watchdog('workflow_notify',
+        '<ul><li>Subject: @subject</li><li>Body: @body</li><li>To: @to</li><li>From: @from</li></ul>', array(
+          '@subject' => $message['subject'],
+          '@body' => implode('<br />', $message['body']),
+          '@to' => $message['to'],
+          '@from' => $message['from'],
+        ),
+        WATCHDOG_INFO, l(t('view'), 'node/' . $params['data']['node']->nid));
+      return;
+  }
+}
+
+
+/**
+ * Calls drupal_mail() to notify users.
+ *
+ * @param $new_state
+ * @param $entity
+ * @param string $entity_type
+ * @param string $field_name
+ * @param null $transition
+ * @param null $user
+ * @throws EntityMalformedException
+ */
+function _workflow_notify($new_state, $entity, $entity_type = '', $field_name = '', $transition = NULL, $user = NULL) {
+  global $user;
+
+  // See if this is a state that we notify for.
+  $notify = variable_get('workflow_notify_roles', array());
+  if (!isset($notify[$new_state])) {
+    return;
+  }
+
+  // The name of the person making the change.
+  $changer = format_username($user);
+  $changer_mail = $user->mail;
+
+  // Okay, we are notifying someone of this change.
+  // So let's get the workflow object.
+  list($entity_id, , $entity_bundle) = entity_extract_ids($entity_type, $entity);
+  /* @var $workflow Workflow */
+  $workflow = workflow_get_workflows_by_type($entity_bundle, $entity_type);
+  $wid = $workflow->getWorkflowId();
+
+    // And all the states.
+  $states = $workflow->getStates(TRUE);
+
+  // Get the specific roles to notify.
+  $notify = $notify[$new_state];
+
+  // See if we want to notify the author too?
+  $notify_author = in_array(-1, $notify);
+  unset($notify[-1]);
+
+  // There could be no roles set.
+  if ($notify) {
+    // Get all the user accounts in those roles.
+    $query = "SELECT DISTINCT ur.uid "
+      . "FROM {users_roles} ur "
+      . "INNER JOIN {users} u ON u.uid = ur.uid "
+      . "WHERE ur.rid IN (:rids) "
+      . "AND u.status = 1 ";
+    $users = db_query($query, array(':rids' => $notify))->fetchCol();
+  } else {
+    $users = array();
+  }
+
+  // Some entities (like Term) have no Author.
+  if ($notify_author && isset($entity->uid)) {
+    $users[] = $entity->uid;
+  }
+
+  // Load all the user entities, making sure there are no duplicates.
+  $accounts = entity_load('user', array_unique($users, SORT_NUMERIC));
+
+  // Call all modules that want to limit the list.
+  $args = array(
+    'users' => $accounts,
+    'node' => $entity,
+    'entity' => $entity, // Preparing for entities, keeping backward compatibility.
+    'entity_type' => $entity_type,
+    'state' => $new_state,
+    'roles' => $notify,
+    'workflow' => $workflow,
+  );
+  foreach (module_implements('workflow_notify') as $module) {
+    $function = $module . '_workflow_notify';
+    $function('users', $args);
+  }
+
+  // Retrieve the remaining list without duplicates.
+  $accounts = $args['users'];
+  $addr_list = array();
+
+  // Just quit if there are no users.
+  if (empty($accounts)) {
+    watchdog('workflow_notify', 'No recipients - email skipped.', array(),
+      WATCHDOG_DEBUG, l(t('view'), 'node/' . $entity_id)); // @todo: other entity types.
+    return;
+  }
+
+  foreach ($accounts as $uid => $account) {
+    $addr_list[] = format_username($account) . '<' . $account->mail . '>';
+  }
+
+  $params = array(
+    'clear' => TRUE,
+    'sanitize' => FALSE,
+    'data' => array(
+      'node' => $entity,
+      'entity' => $entity, // Preparing for entities, keeping backward compatibility.
+      'entity_type' => $entity_type,
+      'user' => $user,
+    ),
+    'filter' => variable_get('workflow_notify_filter_format_' . $wid, 'filtered_html'),
+  );
+
+  // Build the subject and body of the mail.
+  // Token replacement occurs in hook_mail().
+  // @TODO: Currently no translation occurs.
+  $params['context']['subject'] = variable_get("workflow_notify_subject_$new_state",
+    '[node:title] is now "[workflow:workflow-current-state-name]"');
+
+  $params['context']['body'] = variable_get("workflow_notify_body_$new_state",
+    '<a href="[node:url:absolute]">[node:title]</a> is now "@state".');
+
+  switch (variable_get('workflow_notify_from_address_' . $wid, 'site')) {
+    case 'site':
+      $from = variable_get('site_mail', ini_get('sendmail_from'));
+      break;
+
+    case 'changer':
+      $from = $user->mail;
+      break;
+  }
+
+  // Send the email.
+  drupal_mail('workflow_notify',
+    'workflow_notify',
+    implode(', ', $addr_list),
+    language_default(),
+    $params,
+    $from);
+
+  return;
+}

+ 12 - 0
sites/all/modules/contrib/admin/workflow/workflow_notify/workflow_notify_og/workflow_notify_og.info

@@ -0,0 +1,12 @@
+name = Workflow Notify OG
+description = Notify roles by OG groups of Workflow transitions.
+dependencies[] = workflow_notify
+core = 7.x
+package = Workflow
+
+; Information added by Drupal.org packaging script on 2017-01-15
+version = "7.x-2.9+10-dev"
+core = "7.x"
+project = "workflow"
+datestamp = "1484510888"
+

+ 29 - 0
sites/all/modules/contrib/admin/workflow/workflow_notify/workflow_notify_og/workflow_notify_og.install

@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the Workflow module.
+ *
+ */
+
+/**
+ * Implements hook_install().
+ */
+function workflow_notify_og_install() {
+  db_update('system')
+    ->fields(array('weight' => -1))
+    ->condition('name', 'workflow_notify_og')
+    ->condition('type', 'module')
+    ->execute();
+}
+
+/**
+ * Set weight for Workflow_Notify_OG.
+ */
+function workflow_notify_og_update_7000() {
+  db_update('system')
+    ->fields(array('weight' => -1))
+    ->condition('name', 'workflow_notify_og')
+    ->condition('type', 'module')
+    ->execute();
+}

+ 114 - 0
sites/all/modules/contrib/admin/workflow/workflow_notify/workflow_notify_og/workflow_notify_og.module

@@ -0,0 +1,114 @@
+<?php
+/**
+ * @file
+ * Notify roles by OG Groups for Workflow state transitions.
+ */
+
+/**
+ * Implements hook_workflow_notify().
+ * @param $op - The operation (columns, users).
+ * @param $args - The arguments for this call.
+ *    - may be:
+ *      'columns' - the current list of table headings.
+ *      'users' - The current list of users.
+ *      'node' - The current node for getting groups focus.
+ *      'state' - The state the node is moving to.
+ *
+ * @return none - Modify the list by reference.
+ */
+function workflow_notify_og_workflow_notify($op, &$args) {
+  switch ($op) {
+    case 'columns':
+      // Add the column heading for this module.
+      $args['columns']['limit_by_group'] = t('Limit by group');
+      break;
+
+    case 'users':
+      $limit = variable_get('workflow_notify_og', array());
+      // Is this a state we care about?
+      if (isset($limit[$args['state']]) && $limit[$args['state']]) {
+        // Yes, so get the node's groups and make sure it has some.
+        $groups = field_get_items('node', $args['node'], 'og_group_ref');
+        if ($groups) {
+          // Get the list of user accounts and check each one.
+          $accounts = $args['users'];
+          foreach ($accounts as $uid => $account) {
+            $keep = FALSE;
+            // Check each group for user's membership.
+            foreach ($groups as $group) {
+              if (og_is_member('node', $group['target_id'], 'user', $account)) {
+                $keep = TRUE;
+                break;
+              }
+            }
+            // Do we find a group?
+            if ($keep == FALSE) {
+              // No, so remove them from the list.
+              unset($args['users'][$uid]);
+            }
+          }
+        }
+      }
+      break;
+
+    case 'tokens':
+      $groups = field_get_items('node', $args['node'], 'og_group_ref');
+      
+      $query = "SELECT g.entity_id AS gid, n.title AS name "
+        . "FROM {field_data_group_group} g "
+        . "INNER JOIN {node} n ON n.nid = g.entity_id "
+        . "WHERE g.deleted = 0 ";
+      $group_names = db_query($query)->fetchAllKeyed();
+
+      if (!empty($groups)) {
+        $list = array();
+        foreach ($groups as $group) {
+          $list[] = $group_names[$group['target_id']];
+        }
+        $args['tokens']['@groups'] = implode(', ', $list);
+      }
+      break;
+  }
+}
+
+/**
+ * Implements hook_form_alter().
+ * Add a column for limiting user groups by OG group.
+ */
+function workflow_notify_og_form_alter(&$form, &$form_state, $form_id) {
+  switch($form_id) {
+    case 'workflow_notify_settings_form':
+      $limit = variable_get('workflow_notify_og', array());
+
+      // Add a group limit flag to each state.
+      foreach ($form['states'] as $sid => $element) {
+        $form['states'][$sid]['limit_by_group'] = array(
+          '#type' => 'radios',
+          '#options' => array(t('No'), t('Yes')),
+          '#attributes' => array('class' => array('limit-by-group')),
+          '#default_value' => (isset($limit[$sid]) ? $limit[$sid] : 0),
+          );
+      }
+
+      // Add our submission handler.
+      $form['#submit'][] = 'workflow_notify_og_form_submit';
+  }
+}
+
+/**
+ * Submission handler.
+ */
+function workflow_notify_og_form_submit(&$form, &$form_state) {
+  $workflow = $form['#workflow'];
+  $limit = variable_get('workflow_notify_og', array());
+
+  // Check each state for limit flags.
+  foreach ($form_state['values']['states'] as $sid => $values) {
+    $limit[$sid] = $values['limit_by_group'];
+  }
+
+  // Save the new limit flags.
+  variable_set('workflow_notify_og', $limit);
+
+  $form_state['redirect'] = "admin/config/workflow/workflow/$workflow->wid";
+}

+ 4 - 4
sites/all/modules/contrib/admin/workflow/workflow_revert/workflow_revert.info

@@ -1,12 +1,12 @@
 name = Workflow Revert
-description = "Adds an 'Revert' link to the first workflow history row."
+description = "Adds a 'Revert' link to the first workflow history row."
 dependencies[] = workflow
 core = 7.x
 package = Workflow
 
-; Information added by drupal.org packaging script on 2013-07-04
-version = "7.x-1.2"
+; Information added by Drupal.org packaging script on 2017-01-15
+version = "7.x-2.9+10-dev"
 core = "7.x"
 project = "workflow"
-datestamp = "1372980654"
+datestamp = "1484510888"
 

+ 66 - 59
sites/all/modules/contrib/admin/workflow/workflow_revert/workflow_revert.module

@@ -1,4 +1,5 @@
 <?php
+
 /**
  * @file
  * Adds an 'Revert' link to the first workflow history row.
@@ -12,8 +13,8 @@ function workflow_revert_permission() {
     'revert workflow' => array(
       'title' => t('Revert workflow'),
       'description' => t('Allow user to revert workflow state.'),
-      ),
-    );
+    ),
+  );
 }
 
 /**
@@ -24,83 +25,89 @@ function workflow_revert_menu() {
 
   $items['workflow_revert'] = array(
     'title' => 'Workflow Undo',
+    'file' => 'workflow_revert.pages.inc',
     'access arguments' => array('revert workflow'),
     'page callback' => 'drupal_get_form',
     'page arguments' => array('workflow_revert_form'),
     'type' => MENU_CALLBACK,
-    );
+  );
 
   return $items;
 }
 
 /**
- * Menu callback to do the revert function.
+ * Implements hook_rules_event_info().
+ *
+ * @todo: Add support for every entity_type in Revert rules.
  */
-function workflow_revert_form($form, $form_state, $nid = NULL, $sid = NULL) {
-  $args = array('#nid' => $nid, '#sid' => $sid);
-
-  if (drupal_valid_token($_GET['token'], 'workflow_revert ' . $sid)) {
-    $states = workflow_get_workflow_states_all();
-    $node = node_load($nid);
-    $args['#node'] = $node;
-    $question = t('Are you sure you want to revert %title to the "@state" state?', array(
-      '@state' => $states[$sid],
-      '%title' => $node->title,
-      ));
-    return confirm_form($args,
-      $question,
-      "node/$nid",
-      t('The worflow state will be changed.')
-      );
-  }
-  else {
-    watchdog('workflow_revert', 'Invalid token', array(), WATCHDOG_ERROR);
-    drupal_set_message(t('Invalid token. Your information has been recorded.'), 'error');
-    drupal_goto("node/$nid");
-  }
-}
-
-function workflow_revert_form_submit($form, $form_state) {
-  $node = $form['#node'];
-  $comment = t('State reverted');
-  $sid = $form['#sid'];
-
-  // Force the transition because it's probably not valid.
-  workflow_execute_transition($node, $sid, $comment, TRUE);
-  drupal_set_message(t('Workflow reverted.', array()));
-
-  drupal_goto('node/' . $form['#nid'] . '/workflow');
+function workflow_revert_rules_event_info() {
+  $events = array(
+    'workflow_state_reverted' => array(
+      'group' => t('Workflow'),
+      'label' => t('Workflow state reverted'),
+      'variables' => rules_events_node_variables(t('updated content'), TRUE),
+    ),
+  );
+  return $events;
 }
 
 /**
  * Implements hook_workflow_history_alter().
- * Add an 'undo' operation for the most recent history change.
  *
- * @param $variables
- *   The current workflow history information as an array.
- *   'old_sid' - The state ID of the previous state.
- *   'old_state_name' - The state name of the previous state.
- *   'sid' - The state ID of the current state.
- *   'state_name' - The state name of the current state.
- *   'history' - The row from the workflow_node_history table.
+ * Adds an 'undo' operation for the most recent history change.
  *
- * If you want to add additional data, place it in the 'extra' value.
+ * @param array $variables
+ *   The current workflow history information as an array.
+ *   'transition' - a WorkflowTransition object.
  */
-function workflow_revert_workflow_history_alter(&$variables) {
+function workflow_revert_workflow_history_alter(array &$variables) {
+  global $user;
   static $first = TRUE;
+
+  $transition = $variables['transition'];
+
+  // Check access, to avoid that user sees a revert link, but is not allowed to
+  // view the revert form. Use $first to check only once per page.
+  if ($first) {
+    if (!user_access('revert workflow', $user)) {
+      $first = FALSE;
+      return;
+    }
+  }
+
   // Only mark the first row.
   if ($first) {
-    // Let's ask other modules if the reversion is allowed.
-    $node = node_load($variables['history']->nid);
-    $result = module_invoke_all('workflow', 'transition permitted', $variables['sid'], $variables['old_sid'], $node);
-    // Did anybody veto this choice?
-    if (!in_array(FALSE, $result)) {
-      // If not vetoed, mark it.
-      $options = array('query' => array('token' => drupal_get_token('workflow_revert ' . $variables['old_sid'])));
-      $path = 'workflow_revert/' . $variables['history']->nid . '/' . $variables['old_sid'];
-      $variables['extra'] = l('Revert state change', $path, $options);
+    $old_sid = $transition->old_sid;
+    $new_sid = $transition->new_sid;
+
+    // Some states are not fit to revert to. In each of these cases, prohibit
+    // to revert to an even older state.
+    $old_state = $transition->getOldState();
+    if (!$old_state || !$old_state->isActive() || $old_state->isCreationState()) {
+      $first = FALSE;
+    }
+    elseif ($old_sid == $new_sid) {
+      // Do not add the revert link, but allow an even older state.
+    }
+    else {
+      // Let's ask other modules if the reversion is allowed. Reversing old and new sid!
+      // @todo D8: remove, or replace by 'transition pre'. See WorkflowState::getOptions().
+      $entity_type = $transition->entity_type;
+      $entity = $transition->getEntity();
+      $id = $transition->entity_id;
+      $field_name = $transition->field_name;
+      $permitted = module_invoke_all('workflow', 'transition permitted', $new_sid, $old_sid, $entity, $force = FALSE, $entity_type, $field_name, $transition, $user);
+      // Stop if a module says so.
+      if (!in_array(FALSE, $permitted, TRUE)) {
+        // If not vetoed, mark it.
+        $options = array('query' => array('token' => drupal_get_token('workflow_revert ' . $old_sid)));
+        $path = 'workflow_revert/' . $entity_type . '/' . $id . '/' . $field_name . '/' . $old_sid;
+
+        // If you want to add additional data, place it in the 'extra' value.
+        $variables['extra'] = l(t('Revert state change'), $path, $options);
+
+        $first = FALSE;
+      }
     }
-    // That was your one chance...
-    $first = FALSE;
   }
 }

+ 76 - 0
sites/all/modules/contrib/admin/workflow/workflow_revert/workflow_revert.pages.inc

@@ -0,0 +1,76 @@
+<?php
+/**
+ * @file
+ * Contains 'workflow_revert' page.
+ */
+
+/**
+ * Menu callback to do the revert function.
+ *
+ * @todo: add support for Field in workflow_revert.
+ */
+function workflow_revert_form($form, $form_state, $entity_type, $id, $field_name, $previous_sid = NULL) {
+  $entity = entity_load_single($entity_type, $id);
+  $uri = entity_uri($entity_type, $entity);
+  $return_uri = $uri['path'] . '/workflow';
+
+  if (drupal_valid_token($_GET['token'], 'workflow_revert ' . $previous_sid)) {
+    $state = workflow_state_load_single($previous_sid);
+
+    $args['#previous_sid'] = $previous_sid;
+    $args['#entity_id'] = $id;
+    $args['#entity'] = $entity;
+    $args['#entity_type'] = $entity_type;
+    $args['#field_name'] = $field_name;
+    $question = t('Are you sure you want to revert %title to the "@state" state?', array(
+      '@state' => $state->label(),
+      '%title' => entity_label($entity_type, $entity),
+      )
+    );
+    return confirm_form($args,
+      $question,
+      $return_uri,
+      t('The workflow state will be changed.')
+    );
+  }
+  else {
+    watchdog('workflow_revert', 'Invalid token', array(), WATCHDOG_ERROR);
+    drupal_set_message(t('Invalid token. Your information has been recorded.'), 'error');
+    drupal_goto($return_uri);
+  }
+}
+
+/**
+ * Submit callback function.
+ *
+ * The function is magically called, due to its name <form>_submit.
+ */
+function workflow_revert_form_submit($form, $form_state) {
+  global $user;
+
+  $previous_sid = $form['#previous_sid'];
+  // $id = $form['#entity_id'];
+  $entity = $form['#entity'];
+  $entity_type = $form['#entity_type'];
+  $field_name = $form['#field_name'];
+
+  $comment = t('State reverted.');
+
+  $uri = entity_uri($entity_type, $entity);
+  $return_uri = $uri['path'];
+
+  // If Rules is available, signal the reversion.
+  if (module_exists('rules')) {
+    rules_invoke_event('workflow_state_reverted', $entity);
+  }
+
+  $current_sid = workflow_node_current_state($entity, $entity_type, $field_name);
+  $transition = new WorkflowTransition();
+  $transition->setValues($entity_type, $entity, $field_name, $current_sid, $previous_sid, $user->uid, REQUEST_TIME, $comment);
+
+  // Force the transition because it's probably not valid.
+  $new_sid = workflow_execute_transition($entity_type, $entity, $field_name, $transition, TRUE);
+  ($previous_sid == $new_sid) ? drupal_set_message($comment) : drupal_set_message(t('State could not be reverted.'), 'warning');  
+
+  drupal_goto($return_uri . '/workflow');
+}

+ 14 - 0
sites/all/modules/contrib/admin/workflow/workflow_rules/README.txt

@@ -0,0 +1,14 @@
+WORKFLOW NODE
+=============
+When using the conventional 'Workflow Node API', Rules should be triggered upon
+the workflow-specific 'transition post' event.
+
+WORKFLOW FIELD
+==============
+As of Workflow 7.x-2.0, alternative 'Workflow Field API' is available. There
+is no need to enable this module if you use Workflow Field.
+You can add a Rule using:
+- After saving a new workflow scheduled transition
+- After updating an existing Entity
+The 'transition post' hook is not called for Workflow Field, since at that
+moment in the update process, the data is not saved yet.

+ 65 - 0
sites/all/modules/contrib/admin/workflow/workflow_rules/workflow_rules.field.inc

@@ -0,0 +1,65 @@
+<?php
+/**
+ * @file
+ * Rules integration for the Workflow module with Entity API.
+ */
+
+if (module_exists('rules')) {
+  require_once dirname(__FILE__) . '/workflow_rules.rules-callback.inc';
+}
+
+/**
+ * Implements subfunction of hook_rules_condition_info().
+ *
+ * When using "node:" and "node:unchanged", there is no need to create more
+ * conditions to check transitions.
+ */
+// function workflowfield_rules_condition_info() {
+// }
+
+/**
+ * Implements subfunction of hook_rules_action_info().
+ */
+function _workflowfield_rules_action_info() {
+  $actions = array();
+
+  // Warning: keep this action in line between Workflow Field and Workflow Node.
+  $actions['workflowfield_field_set_state'] = array(
+    'group' => t('Workflow'),
+    'label' => t('Set a Workflow state (with a comment)'),
+    'parameter' => array(
+      // "parameter['node']" is for backwards compatibility: can be any entity_type.
+      'node' => array(
+        'type' => 'entity',
+        'label' => t('Entity'),
+        'description' => t('The entity to set the current workflow state of.'),
+        // 'save' => TRUE,
+      ),
+      'field' => array(
+        'type' => WORKFLOWFIELD_PROPERTY_TYPE,
+        'label' => t('Workflow field to set'),
+        'description' => t('The workflow field to set.'),
+        'restriction' => 'selector',
+        // 'allow null' => TRUE,
+      ),
+      'workflow_state' => array(
+        'type' => 'list<integer>',
+        'label' => t('New workflow state'),
+        'options list' => '_workflow_rules_workflow_get_options',
+        'description' => t('The workflow state to set (select only one).'),
+      ),
+      'workflow_comment' => array(
+        'type' => 'text',
+        'label' => t('Workflow Comment'),
+        'description' => t('The workflow comment to set.'),
+        'optional' => TRUE,
+      ),
+    ),
+    'named parameter' => TRUE,
+    'base' => '_workflow_rules_set_state',
+    'callbacks' => array(
+      'execute' => '_workflow_rules_set_state',
+    ),
+  );
+  return $actions;
+}

+ 11 - 7
sites/all/modules/contrib/admin/workflow/workflow_rules/workflow_rules.info

@@ -1,12 +1,16 @@
-name = Workflow rules
-description = Provides rules integration for workflows.
+name = Workflow Rules
+description = Provides additional, workflow-specific Rules integration.
+core = 7.x
+package = Workflow
+
 dependencies[] = workflow
 dependencies[] = rules
-package = Workflow
-core = 7.x
-; Information added by drupal.org packaging script on 2013-07-04
-version = "7.x-1.2"
+
+configure = admin/config/workflow/rules
+
+; Information added by Drupal.org packaging script on 2017-01-15
+version = "7.x-2.9+10-dev"
 core = "7.x"
 project = "workflow"
-datestamp = "1372980654"
+datestamp = "1484510888"
 

+ 8 - 27
sites/all/modules/contrib/admin/workflow/workflow_rules/workflow_rules.module

@@ -1,35 +1,16 @@
 <?php
 /**
  * @file
- * Provide rules for workflows.
+ * Provides Rules integration for Workflow.
+ *
  * Why it's own module? Some sites prefer rules, some prefer actions,
  * all prefer a lower code footprint and better performance.
- * Additional creadit to gcassie ( http://drupal.org/user/80260 ) for
+ * Additional credit to gcassie ( http://drupal.org/user/80260 ) for
  * the initial push to split rules out of core workflow.
- */
-
-
-/**
- * Implements hook_workflow().
  *
- * @param $op
- *   The current workflow operation: 'transition pre' or 'transition post'.
- * @param $old_state
- *   The state ID of the current state.
- * @param  $new_state
- *   The state ID of the new state.
- * @param $node
- *   The node whose workflow state is changing.
+ * This file is intentionally left blanc.
+ * The code is in the workflow_rules.workflow.inc file.
+ * Drupal 7 requires a .module file, even if it is empty.
  */
-function workflow_rules_workflow($op, $old_state, $new_state, $node) {
-  switch ($op) {
-    case 'transition post':
-      // Rules are updated on after the transition.
-      if ($old_state == $new_state) {
-        rules_invoke_event('workflow_comment_added', $node, $old_state, $new_state);
-        return;
-      }
-      rules_invoke_event('workflow_state_changed', $node);
-    break;
-  }
-}
+
+require_once dirname(__FILE__) . '/workflow_rules.rules-callback.inc';

+ 153 - 0
sites/all/modules/contrib/admin/workflow/workflow_rules/workflow_rules.node.inc

@@ -0,0 +1,153 @@
+<?php
+/**
+ * @file
+ * Rules integration for the Workflow module with Node API.
+ */
+
+/**
+ * Implements subfunction of hook_rules_event_info().
+ */
+function _workflownode_rules_event_info() {
+  $label = t('updated content');
+  // Add variables for the 'node' type.
+  $node_variables = rules_events_node_variables($label, TRUE);
+
+  $events = array(
+    'workflow_state_changed' => array(
+      'group' => t('Workflow'),
+      'label' => t('Workflow state has changed'),
+      'variables' => $node_variables,
+    ),
+    'workflow_comment_added' => array(
+      'group' => t('Workflow'),
+      'label' => t('Workflow comment was added, but state did not change'),
+      'variables' => $node_variables,
+    ),
+  );
+  return $events;
+}
+
+/**
+ * Implements subfunction of hook_rules_condition_info().
+ */
+function _workflownode_rules_condition_info() {
+  return array(
+    'workflow_check_transition' => array(
+      'group' => t('Workflow'),
+      'label' => t('Content makes a specific transition'),
+      'parameter' => array(
+        'node' => array(
+          'type' => 'node',
+          'label' => t('Node'),
+          'description' => t('The node whose workflow state is being checked.'),
+        ),
+        'old_state' => array(
+          'type' => 'list<integer>',
+          'label' => t('Old workflow state'),
+          'options list' => '_workflow_rules_workflow_get_options',
+          'description' => t('The workflow state moved from.'),
+        ),
+        'new_state' => array(
+          'type' => 'list<integer>',
+          'label' => t('New workflow state'),
+          'options list' => '_workflow_rules_workflow_get_options',
+          'description' => t('The workflow state moved to.'),
+        ),
+      ),
+      'base' => '_workflow_rules_node_check_transition',
+      'callbacks' => array(
+        'execute' => '_workflow_rules_node_check_transition',
+      ),
+    ),
+    'workflow_check_state' => array(
+      'group' => t('Workflow'),
+      'label' => t('Content has a workflow state'),
+      'parameter' => array(
+        'node' => array(
+          'type' => 'node',
+          'label' => t('Node'),
+          'description' => t('The node to compare the current workflow state of.'),
+        ),
+        'workflow_state' => array(
+          'type' => 'list<integer>',
+          'label' => t('Compare workflow state'),
+          'options list' => '_workflow_rules_workflow_get_options',
+          'description' => t('The possible workflow states to compare against.'),
+        ),
+      ),
+      'base' => '_workflow_rules_node_check_state',
+      'callbacks' => array(
+        'execute' => '_workflow_rules_node_check_state',
+      ),
+    ),
+    'workflow_check_previous_state' => array(
+      'group' => t('Workflow'),
+      'label' => t('Content has a previous workflow state'),
+      'parameter' => array(
+        'node' => array(
+          'type' => 'node',
+          'label' => t('Node'),
+          'description' => t('The node to compare the previous workflow state of.'),
+        ),
+        'workflow_state' => array(
+          'type' => 'list<integer>',
+          'label' => t('Compare workflow state'),
+          'options list' => '_workflow_rules_workflow_get_options',
+          'description' => t('The possible workflow states to compare against.'),
+        ),
+      ),
+      'base' => '_workflow_rules_node_check_previous_state',
+      'callbacks' => array(
+        'execute' => '_workflow_rules_node_check_previous_state',
+      ),
+    ),
+  );
+}
+
+/**
+ * Implements subfunction of hook_rules_action_info().
+ */
+function _workflownode_rules_action_info() {
+  $actions = array();
+
+  // Warning: keep this action in line between Workflow Field and Workflow Node.
+  $actions['workflow_rules_set_state'] = array(
+    'group' => t('Workflow'),
+    'label' => t('Set a Workflow state (with a comment)'),
+    'parameter' => array(
+      'node' => array(
+        'type' => 'entity',
+        'label' => t('Node'),
+        'description' => t('The node to set the current workflow state of.'),
+        // 'save' => TRUE,
+      ),
+/*
+      //'field' => array(
+      //  'type' => 'text', // WORKFLOWFIELD_PROPERTY_TYPE,
+      //  'label' => t('Workflow field to set'),
+      //  'description' => t('The workflow field to set.'),
+      //  'restriction' => 'selector',
+      //  'allow null' => TRUE,
+      //),
+ */
+      'workflow_state' => array(
+        'type' => 'list<integer>',
+        'label' => t('New workflow state'),
+        'options list' => '_workflow_rules_workflow_get_options',
+        'description' => t('The workflow state to set (select only one).'),
+      ),
+      'workflow_comment' => array(
+        'type' => 'text',
+        'label' => t('Workflow Comment'),
+        'description' => t('The workflow comment to set.'),
+        'optional' => TRUE,
+      ),
+    ),
+    'named parameter' => TRUE,
+    'base' => '_workflow_rules_set_state',
+    'callbacks' => array(
+      'execute' => '_workflow_rules_set_state',
+    ),
+  );
+  return $actions;
+}

+ 156 - 0
sites/all/modules/contrib/admin/workflow/workflow_rules/workflow_rules.rules-callback.inc

@@ -0,0 +1,156 @@
+<?php
+/**
+ * @file
+ * Callback implementations for Rules integration for the Workflow module.
+ *
+ * These callbacks must be included in the module file.
+ * It is not sufficient to have them in the rules_info file.
+ */
+
+/**
+ * Helper function to parse a token "node:<field_name>".
+ *
+ * @param string $token
+ *
+ * @return string $field_name
+ *
+ * This is a poorman's effort to convert a token into a field name.
+ */
+function _workflow_rules_token_replace($field_name) {
+  // Get the part after the first domain indicator.
+  $list_parts = explode(':', $field_name, 2);
+  $field_name = end($list_parts);
+
+  $field_name = str_replace('-', '_', $field_name);
+  return $field_name;
+}
+
+/**
+ * Condition callback: gather all workflow states, to show in list_options.
+ */
+function _workflow_rules_workflow_get_options($data) {
+  // This is a poorman's effort to convert a token into a field name.
+  $field_name = isset($data->settings['field:select']) ? _workflow_rules_token_replace($data->settings['field:select']) : '';
+  $field = _workflow_info_field($field_name, NULL);
+
+  $options['ANY'] = 'ANY state';
+  $options += workflow_get_workflow_state_names($field['settings']['wid'], $grouped = TRUE);
+  return $options;
+}
+
+/**
+ * Condition implementation helper function: check given state.
+ *
+ * @param mixed $sid
+ *   A State ID, to compare with the given list of allowed State ID's.
+ * @param array $sids
+ *   A list of allowed State ID's.
+ *
+ * @return bool
+ *   TRUE or FALSE.
+ */
+function _workflow_rules_workflow_check_given_state($sid, array $sids) {
+  return in_array('ANY', $sids) || in_array($sid, $sids);
+}
+
+/**
+ * Condition implementation: check state transition..
+ *
+ * Only for Workflow Node! Workflow Field can use default Rules condition.
+ *
+ * @param object $node
+ *   The node with the new values. Other entity types are not supported.
+ * @param array $old_sids
+ *   An array of old sids to meet the condition.
+ * @param array $new_sids
+ *   An array of new sids to meet the condition.
+ * @param array $condition
+ *   A RulesCondition->settings array.
+ *
+ * @return bool
+ */
+function _workflow_rules_node_check_transition($node, array $old_sids, array $new_sids, array $condition) {
+  if (!$last_transition = workflow_transition_load_single('node', $node->nid, '')) {
+    return FALSE;
+  }
+
+  $old_sid = $last_transition->old_sid;
+  $new_sid = $last_transition->new_sid;
+
+  return
+    _workflow_rules_workflow_check_given_state($old_sid, $old_sids) &&
+    _workflow_rules_workflow_check_given_state($new_sid, $new_sids);
+}
+
+/**
+ * Condition implementation: check current state for Workflow Node API.
+ *
+ * Only for Workflow Node! Workflow Field can use default Rules condition.
+ *
+ * @param object $node
+ *   The node with the new values. Other entity types are not supported.
+ * @param array $sids
+ *   An array of State IDs to meet the condition.
+ *
+ * @return bool
+ */
+function _workflow_rules_node_check_state($node, array $sids) {
+  // Provide a fast exit if this is a node type without Workflow.
+  // workflow_node_current_state() will return CreationState otherwise.
+  if (!isset($node->workflow)) {
+    return FALSE;
+  }
+
+  $field_name = ''; // An explicit var is needed.
+  $sid = workflow_node_current_state($node, 'node', $field_name);
+  return _workflow_rules_workflow_check_given_state($sid, $sids);
+}
+
+/**
+ * Condition implementation: check previous state.
+ *
+ * Only for Workflow Node! Workflow Field can use default Rules condition.
+ */
+function _workflow_rules_node_check_previous_state($node, $sids) {
+  if (!$last_transition = workflow_transition_load_single('node', $node->nid, '')) {
+    return FALSE;
+  }
+
+  $sid = $last_transition->old_sid;
+  return _workflow_rules_workflow_check_given_state($sid, $sids);
+}
+
+/**
+ * Action implementation: set current state, ignoring current user permission.
+ *
+ * For both Workflow Node and Workflow Field.
+ */
+function _workflow_rules_set_state(array $parameters, RulesAction $action) {
+  global $user;
+
+  // Warning: keep this action in line between Workflow Field and Workflow Node.
+  // "$parameters['node']" is for backwards compatibility: can be any entity_type.
+  $entity = $parameters['node']->value(); // $parameters['entity'] is an EntityDrupalWrapper.
+  $entity_type = $parameters['node']->type();
+
+  // This is a poorman's effort to convert a token into a field name.
+  $field_name = isset($parameters['settings']['field:select']) ? _workflow_rules_token_replace($parameters['settings']['field:select']) : '';
+  $old_sid = workflow_node_current_state($entity, $entity_type, $field_name);
+
+  // Select the last state on the list.
+  $new_sid = array_pop($parameters['workflow_state']);
+  if ($new_sid == 'ANY') {
+    $new_sid = $old_sid;
+  }
+  $comment = $parameters['workflow_comment'];
+
+  $transition = new WorkflowTransition();
+  $transition->setValues($entity_type, $entity, $field_name, $old_sid, $new_sid, $user->uid, REQUEST_TIME, $comment);
+  // Force this transition, to ignore the limitations on the current user's permissions.
+  $transition->force(TRUE);
+
+  // Execute the transition. It may bounce, due to extra checks.
+  // @todo: use $new_sid = $transition->execute() without generating infinite loops.
+  // Below method does not recalc tokens on automatic_entity_label.
+  $new_sid = workflow_execute_transition($entity_type, $entity, $field_name, $transition);
+}

+ 30 - 185
sites/all/modules/contrib/admin/workflow/workflow_rules/workflow_rules.rules.inc

@@ -1,25 +1,31 @@
 <?php
 /**
  * @file
- * Rules integration for the Workflow module
+ * Rules integration for the Workflow module.
+ *
+ * Contains _info() hooks.
+ * Callbacks are implemented in file workflow.rules.inc.
  */
 
+/*
+ * Include the Condition and Actions for Nodes and Entity.
+ * They are in separate files, but must be kept in sync.
+ * They contain separate logic for the 'conventional' Workflow Node API
+ * and the 'new' Workfow Field API.
+ */
+require_once dirname(__FILE__) . '/workflow_rules.node.inc';
+require_once dirname(__FILE__) . '/workflow_rules.field.inc';
+
 /**
  * Implements hook_rules_event_info().
+ *
+ * @todo: add support for any entity type in hook_rules_event_info.
  */
 function workflow_rules_rules_event_info() {
-  $events = array(
-    'workflow_state_changed' => array(
-      'group' => t('Workflow'),
-      'label' => t('Workflow state has changed'),
-      'variables' => rules_events_node_variables(t('updated content'), TRUE),
-    ),
-    'workflow_comment_added' => array(
-      'group' => t('Workflow'),
-      'label' => t('Workflow comment was added, but state did not change'),
-      'variables' => rules_events_node_variables(t('updated content'), TRUE),
-    ),
-  );
+  $events = array();
+  if (module_exists('workflownode')) {
+    $events += _workflownode_rules_event_info();
+  }
   return $events;
 }
 
@@ -27,184 +33,23 @@ function workflow_rules_rules_event_info() {
  * Implements hook_rules_condition_info().
  */
 function workflow_rules_rules_condition_info() {
-  return array(
-    'workflow_check_transition' => array(
-      'group' => t('Workflow'),
-      'label' => t('Check workflow transition'),
-      'parameter' => array(
-        'node' => array(
-          'type' => 'node',
-          'label' => t('Node'),
-          'description' => t('The node whose workflow state is being checked.'),
-        ),
-        'old_state' => array(
-          'type' => 'list<integer>',
-          'label' => t('Old workflow state'),
-          'options list' => '_workflow_rules_condition_select',
-          'description' => t('The workflow state moved from.'),
-        ),
-        'new_state' => array(
-          'type' => 'list<integer>',
-          'label' => t('New workflow state'),
-          'options list' => '_workflow_rules_condition_select',
-          'description' => t('The workflow state moved to.'),
-        ),
-      ),
-    ),
-    'workflow_check_state' => array(
-      'group' => t('Workflow'),
-      'label' => t('Content has a workflow state'),
-      'parameter' => array(
-        'node' => array(
-          'type' => 'node',
-          'label' => t('Node'),
-          'description' => t('The node to compare the current workflow state of.'),
-        ),
-        'workflow_state' => array(
-          'type' => 'list<integer>',
-          'label' => t('Compare workflow state'),
-          'options list' => '_workflow_rules_condition_select',
-          'description' => t('The possible workflow states to compare against.'),
-        ),
-      ),
-    ),
-    'workflow_check_previous_state' => array(
-      'group' => t('Workflow'),
-      'label' => t('Content has a previous workflow state'),
-      'parameter' => array(
-        'node' => array(
-          'type' => 'node',
-          'label' => t('Node'),
-          'description' => t('The node to compare the previous workflow state of.'),
-        ),
-        'workflow_state' => array(
-          'type' => 'list<integer>',
-          'label' => t('Compare workflow state'),
-          'options list' => '_workflow_rules_condition_select',
-          'description' => t('The possible workflow states to compare against.'),
-        ),
-      ),
-    ),
-  );
+  $conditions = array();
+  if (module_exists('workflownode')) {
+    $conditions += _workflownode_rules_condition_info();
+  }
+  return $conditions;
 }
 
 /**
  * Implements hook_rules_action_info().
  */
 function workflow_rules_rules_action_info() {
-  return array(
-    'workflow_rules_set_state' => array(
-      'group' => t('Workflow'),
-      'label' => t('Set workflow state for content'),
-      'parameter' => array(
-        'node' => array(
-          'type' => 'node',
-          'label' => t('Node'),
-          'description' => t('The node to set the current workflow state of.'),
-//          'save' => TRUE,
-        ),
-        'workflow_state' => array(
-          'type' => 'list<integer>',
-          'label' => t('New workflow state'),
-          'options list' => '_workflow_rules_action_select',
-          'description' => t('The workflow state to set (select only one).'),
-        ),
-        'workflow_comment' => array(
-          'type' => 'text',
-          'label' => t('Workflow Comment'),
-          'description' => t('The workflow comment to set.'),
-          'optional' => TRUE,
-        ),
-      ),
-    ),
-  );
-}
-
-/**
- * Condition callback: gather all workflow states.
- */
-function _workflow_rules_condition_select() {
-  $options['ANY'] = 'ANY state';
-  foreach (workflow_get_workflows() as $workflow) {
-    foreach (workflow_get_workflow_states_by_wid($workflow->wid) as $state) {
-      $states[$state->sid] = check_plain($workflow->name) . ': ' . check_plain($state->state);
-    }
-  }
-  $options = $options + $states;
-  return $options;
-}
-
-/**
- * Condition callback: gather all workflow states.
- */
-function _workflow_rules_action_select() {
-  foreach (workflow_get_workflows() as $workflow) {
-    foreach (workflow_get_workflow_states_by_wid($workflow->wid) as $state) {
-      $states[$state->sid] = check_plain($workflow->name) . ': ' . check_plain($state->state);
-    }
-  }
-  return $states;
-}
-
-/**
- * Condition implementation: check state transition.
- */
-function workflow_check_transition($node, $old_states, $new_states) {
-  $node_current_state = workflow_node_current_state($node);
-  $node_old_state = workflow_node_previous_state($node);
-
-  if (in_array('ANY', $old_states)) {
-    if (in_array('ANY', $new_states)) {
-      return TRUE;
-    }
-    return in_array($node_current_state, $new_states);
-  }
-
-  if (in_array('ANY', $new_states)) {
-    return in_array($node_old_state, $old_states);
-  }
-  return in_array($node_old_state, $old_states) && in_array($node_current_state, $new_states);
-}
-
-/**
- * Condition implementation: check current state.
- */
-function workflow_check_state($node, $states) {
-  $node_state = workflow_node_current_state($node);
-  return workflow_check_given_state($node, $states, $node_state);
-}
-
-/**
- * Condition implementation: check previous state.
- */
-function workflow_check_previous_state($node, $states) {
-  $node_state = workflow_node_previous_state($node);
-  return workflow_check_given_state($node, $states, $node_state);
-}
-
-/**
- * Condition implementation helper function: check given state.
- */
-function workflow_check_given_state($node, $states, $node_state) {
-  if (in_array('ANY', $states)) {
-    return TRUE;
-  }
-
-  if (in_array($node_state, $states)) {
-    return TRUE;
+  $actions = array();
+  if (module_exists('workflownode')) {
+    $actions += _workflownode_rules_action_info();
   }
-  return FALSE;
-}
-
-/**
- * Action implementation: set current state, ignoring current user permissione.
- */
-function workflow_rules_set_state($node, $states, $comment = NULL) {
-  // Select the last state on the list.
-  $sid = array_pop($states);
-  if (!empty($comment)) {
-    $node->workflow_comment = $comment;
+  if (module_exists('workflowfield')) {
+    $actions += _workflowfield_rules_action_info();
   }
-  workflow_transition($node, $sid, TRUE);
-  unset($node->workflow_comment);
+  return $actions;
 }

+ 39 - 0
sites/all/modules/contrib/admin/workflow/workflow_rules/workflow_rules.workflow.inc

@@ -0,0 +1,39 @@
+<?php
+/**
+ * @file
+ * Provide rules for workflows via hook_workflow.
+ */
+
+/**
+ * Implements hook_workflow().
+ *
+ * Invokes events, as defined in hook_rules_event_info().
+ *
+ * @param string $op
+ *   The current workflow operation: 'transition pre' or 'transition post'.
+ * @param int $old_sid
+ *   The state ID of the current state.
+ * @param int $new_sid
+ *   The state ID of the new state.
+ * @param object $entity
+ *   The entity whose workflow state is changing.
+ * @param bool $force
+ */
+function workflow_rules_workflow($op, $old_sid, $new_sid, $entity, $force = FALSE, $entity_type = '', $field_name = '', $transition = NULL) {
+  switch ($op) {
+    case 'transition post':
+      // Rules are updated only after a transition of a Workflow Node status.
+      // When using Workflow Field, this hook is not called. Use default Rules
+      // data instead.
+      if ($old_sid == $new_sid) {
+        rules_invoke_event('workflow_comment_added', $entity, $entity_type, $old_sid, $new_sid);
+      }
+      else {
+        rules_invoke_event('workflow_state_changed', $entity, $entity_type, $old_sid, $new_sid);
+      }
+      break;
+
+    default:
+      break;
+  }
+}

+ 6 - 5
sites/all/modules/contrib/admin/workflow/workflow_search_api/workflow_search_api.info

@@ -1,13 +1,14 @@
-name = Workflow Search API
-description = "Adds workflow state information to Search API index"
+name = Workflow Node Search API
+description = "Adds Workflow Node's state information to Search API index."
 dependencies[] = workflow
+dependencies[] = workflownode
 dependencies[] = entity
 core = 7.x
 package = Workflow
 
-; Information added by drupal.org packaging script on 2013-07-04
-version = "7.x-1.2"
+; Information added by Drupal.org packaging script on 2017-01-15
+version = "7.x-2.9+10-dev"
 core = "7.x"
 project = "workflow"
-datestamp = "1372980654"
+datestamp = "1484510888"
 

+ 10 - 14
sites/all/modules/contrib/admin/workflow/workflow_search_api/workflow_search_api.module

@@ -2,6 +2,8 @@
 /**
  * @file
  * Adds workflow state information to Search API index.
+ *
+ * This is only valid for Workflow Node, not Workflow Field.
  */
 
 /**
@@ -10,24 +12,18 @@
 function workflow_search_api_entity_property_info_alter(&$info) {
   $info['node']['properties']['workflow_state_name'] = array(
     'type' => 'text',
-    'label' => t('Workflow state name'),
+    'label' => t('Workflow state label'),
     'sanitized' => TRUE,
     'getter callback' => 'workflow_search_api_property_workflow_state_getter_callback',
-    );
+  );
 }
 
 /**
  * Getter callback for workflow state defined in workflow_search_api_entity_property_info_alter.
  */
-function workflow_search_api_property_workflow_state_getter_callback($item) {
-  if ($item->workflow) {
-    // Get text value of workflow state.
-    $state = workflow_get_workflow_states_by_sid($item->workflow);
-    $state_name = $state->state;
-  }
-  else {
-    $state_name = '';
-  }
-
-  return $state_name;
-}
+function workflow_search_api_property_workflow_state_getter_callback($node) {
+  // Get text value of workflow state. Only for Workflow Node.
+  $field_name = ''; // An explicit var is needed.
+  $sid = workflow_node_current_state($node, 'node', $field_name);
+  return workflow_get_sid_label($sid);
+}

+ 3 - 3
sites/all/modules/contrib/admin/workflow/workflow_vbo/README.txt

@@ -22,7 +22,7 @@ the content type.
 
 So: KEEP YOUR CONTENT TYPE NAMES SHORT.
 
-Unfortunately the code that handles this is in core, so not readily changable. If
+Unfortunately the code that handles this is in core, so not readily changeable. If
 you have trouble seeing your actions check your name lengths.
 
 See further discussion at:
@@ -33,5 +33,5 @@ See request put to core to make the change at:
   http://drupal.org/node/1062068
 
   Closed and told to have workflow make the table change ourselves. Given that changing
-  name lengths haphazardly would spread the bugs around even more this approach was
-  not followed.
+  name lengths hazardly would spread the bugs around even more this approach was
+  not followed.

+ 260 - 0
sites/all/modules/contrib/admin/workflow/workflow_vbo/actions/given.action.inc

@@ -0,0 +1,260 @@
+<?php
+
+/**
+ * @file
+ * VBO action to modify entity values (properties and fields).
+ */
+
+/**
+ * Implements hook_action_info().
+ *
+ * Registers custom VBO actions as Drupal actions.
+ */
+function workflow_vbo_given_action_info() {
+  return array(
+    'workflow_vbo_given_state_action' => array(
+      'type' => 'entity',
+      'label' => t('Change workflow state of post to new state'),
+      'configurable' => TRUE,
+      'triggers' => array('any'),
+//      'aggregate' => TRUE,
+//      'pass rows' => TRUE, // you will have $context['views row'] as the current selected row.
+    ),
+  );
+}
+
+/**
+ * Implements a Drupal action. Move a node to a specified state in the workflow.
+ * @param $entity
+ * @param array $context
+ */
+function workflow_vbo_given_state_action($entity, array $context) {
+  global $user;
+
+// As advanced action with Trigger 'node':
+// - $entity is empty;
+// - $context['group'] = 'node'
+// - $context['hook'] = 'node_insert / _update / _delete'
+// - $context['node'] = (Object) stdClass
+// - $context['entity_type'] = NULL
+
+// As advanced action with Trigger 'taxonomy':
+// - $entity is (Object) stdClass;
+// - $context['type'] = 'entity'
+// - $context['group'] = 'taxonomy'
+// - $context['hook'] = 'taxonomy_term_insert / _update / _delete'
+// - $context['node'] = (Object) stdClass
+// - $context['entity_type'] = NULL
+
+// As advanced action with Trigger 'workflow API':
+// ...
+
+// As VBO action:
+// - $entity is (Object) stdClass;
+// - $context['type'] = NULL
+// - $context['group'] = NULL
+// - $context['hook'] = NULL
+// - $context['node'] = (Object) stdClass
+// - $context['entity_type'] = 'node'
+
+  // Get the entity type, entity and entity ID.
+  if (isset($context['entity_type'])) {
+    // In a VBO Action.
+    $entity_type = $context['entity_type'];
+  }
+  else {
+    // In an Advanced Action.
+    $entity_type = str_replace(array('_insert', '_update' , '_delete'), '', $context['hook']);
+  }
+  // Change the state of latest revision, not current revision.
+  if (isset($context[$entity_type])) {
+    $entity = $context[$entity_type];
+  }
+  elseif (!isset($entity)) {
+    $entity = $context['node'];
+  }
+  // In 'after saving new content', the node is already saved. Avoid second insert.
+  // Todo: clone?
+  unset($entity->is_new);
+
+  list($entity_id, , $entity_bundle) = entity_extract_ids($entity_type, $entity);
+  if (!$entity_id) {
+    watchdog('workflow_vbo', 'Unable to get current entity ID - entity is not yet saved.');
+    return;
+  }
+
+  // Get data from the context.
+  $form = $context['form'];
+  $form_state = $context['form_state'];
+
+  // Get the current State Id. Also, $field_name will be set magically, by reference.
+  $field_name = NULL;
+  $current_sid = workflow_node_current_state($entity, $entity_type, $field_name);
+  if (!$current_sid) {
+    watchdog('workflow_vbo', 'Unable to get current workflow state of entity %id.',
+      array('%id' => $entity_id));
+    return;
+  }
+
+  // Get the new State Id.
+  $new_sid = $form_state['input']['workflow_sid'];
+
+  // The following 2 lines should give the same result.
+  // $force = $form_state['input']['workflow_force'];
+  $force = $context['force'];
+
+  // Get the Comment. Parse the $comment variables.
+  $comment_string = $form_state['input']['workflow_comment'];
+  $comment = t($comment_string, array(
+      '%title' => entity_label($entity_type, $entity), // "@" and "%" will automatically run check_plain().
+      '%state' => workflow_get_sid_label($new_sid),
+      '%user' => $user->name,
+    )
+  );
+
+  // Fire the transition.
+  $transition = new WorkflowTransition();
+  $transition->setValues($entity_type, $entity, $field_name, $current_sid, $new_sid, $user->uid, REQUEST_TIME, $comment);
+  workflow_execute_transition($entity_type, $entity, $field_name, $transition, $force);
+}
+
+/**
+ * Configuration form for "Change workflow state of post to new state" action.
+ *
+ * This copies functionality from workflow_tab_page, and overrides some fields.
+ *
+ * @see workflow_vbo_given_state_action()
+ */
+function workflow_vbo_given_state_action_form(array $context) {
+  $form = array();
+
+  // If we are on admin/config/system/actions and use CREATE AN ADVANCED ACTION
+  // Then $context only contains:
+  // - $context['actions_label'] = "Change workflow state of post to new state"
+  // - $context['actions_type'] = "entity"
+  //
+  // If we are on a VBO action form, then $context only contains:
+  // - $context['entity_type'] = "node"
+  // - $context['view'] = "(Object) view"
+  // - $context['settings'] = "array()"
+
+
+  // @todo: There's a problem here: we do not know the node types of the
+  // selected items, and we do not know the field_names, so we have no clue
+  // about the allowed workflows or states.
+  if ($entity_type = isset($context['entity_type']) ? $context['entity_type'] : NULL) {
+    $entity_info = entity_get_info($entity_type);
+    $entity_key = $entity_info['entity keys']['id'];
+    unset($entity_info);
+  }
+  $result = isset($context['view']) ? $context['view']->result : array();
+
+  // Get the common workflow from entities. With Workflow Node, we knew how to do this.
+  // With Workflow Field, we need to determine the workflow.
+  $wid = 0;
+  $field_name = NULL;
+  foreach ($result as $entity_data) {
+    // @todo: what to do with multiple workflow_fields per bundle?
+    // The 'result' does not contain 'real' entities, and they mess up subfunctions.
+    // We could fetch it from the array, like this: $entity = $entity_data->_field_data[$entity_key]['entity'];
+    // But this is not reliable, if you have no fields to show.
+    // So, last resort, just fetch from database/cache.
+    $entity = entity_load_single($entity_type, $entity_data->{$entity_key});
+    $field_name = NULL;
+    $current_sid = workflow_node_current_state($entity, $entity_type, $field_name);
+    $state = workflow_state_load_single($current_sid);
+    if (!$wid && $state) {
+      $wid = $state->wid;
+    }
+    elseif ($wid && $state && $wid <> $state->wid) {
+      watchdog('workflow', 'Multiple workflows found in VBO action.', WATCHDOG_ERROR);
+      drupal_set_message(t('Error: the selection contains more then one workflow.'), 'error');
+
+      return $form; // <--- exit !!
+    }
+  }
+
+  // Preserve $entity's bundle and id, if only 1 is selected.
+  if (count($result) != 1) {
+    $entity = NULL;
+  }
+  $entity_id = '';
+  $entity_bundle = '';
+
+  // Get the common Workflow, or create a dummy Workflow.
+  $workflow = $wid ? workflow_load($wid) : workflow_create('dummy VBO');
+  // Show the current state and the Workflow form to allow state changing.
+  // N.B. This part is replicated in hook_node_view, workflow_tab_page, workflow_vbo.
+  if ($workflow) {
+    $field = _workflow_info_field($field_name, $workflow);
+    $field_name = $field['field_name'];
+    $field_id = $field['id'];
+    $instance = field_info_instance($entity_type, $field_name, $entity_bundle);
+
+    // Hide the submit button. VBO has its own 'next' button.
+    $instance['widget']['settings']['submit_function'] = '';
+    if (!$field_id) {
+      // This is a Workflow Node workflow. Set widget options as in v7.x-1.2
+      $field['settings']['widget']['comment'] = isset($workflow->options['comment_log_tab']) ? $workflow->options['comment_log_tab'] : 1; // vs. ['comment_log_node'];
+      $field['settings']['widget']['current_status'] = TRUE;
+      // As stated above, the options list is probably very long, so let's use select list.
+      $field['settings']['widget']['options'] = 'select';
+      // Do not show the default [Update workflow] button on the form.
+      $instance['widget']['settings']['submit_function'] = '';
+    }
+  }
+
+  // Add the form/widget to the formatter, and include the nid and field_id in the form id,
+  // to allow multiple forms per page (in listings, with hook_forms() ).
+  // Ultimately, this is a wrapper for WorkflowDefaultWidget.
+  // $form['workflow_current_state'] = workflow_state_formatter($entity_type, $entity, $field, $instance);
+  $form_id = implode('_', array('workflow_transition_form', $entity_type, $entity_id, $field_id));
+  $form += drupal_get_form($form_id, $field, $instance, $entity_type, $entity);
+
+  if (!$entity) {
+    // For the Advanced actions form on admin/config/system/actions, 
+    // remove the Submit function.
+    unset($form['#submit']);
+  }
+
+  // Make adaptations for VBO-form:
+  // Override the options widget.
+  $form['workflow']['workflow_sid']['#title'] = t('Target state');
+  $form['workflow']['workflow_sid']['#description'] = t('Please select the state that should be assigned when this action runs.');
+  $form['workflow']['workflow_sid']['#default_value'] = isset($context['target_sid']) ? $context['target_sid'] : '';
+
+  $form['workflow']['workflow_force'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Force transition'),
+    '#description' => t('If this box is checked, the new state will be assigned even if workflow permissions disallow it.'),
+    '#default_value' => isset($context['force']) ? $context['force'] : '',
+  );
+
+  $form['workflow']['workflow_comment'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Message'),
+    '#description' => t('This message will be written into the workflow history log when the action
+      runs. You may include the following variables: %state, %title, %user.'),
+    '#default_value' => isset($context['workflow_history']) ? $context['workflow_history'] : t('Action set %title to %state by %user.'),
+  );
+
+  return $form;
+}
+
+/**
+ * Submit handler for "Change workflow state of post to new state" action configuration form.
+ *
+ * @see workflow_vbo_given_state_action_form()
+ */
+function workflow_vbo_given_state_action_submit($form, $form_state) {
+  $new_sid = $form_state['input']['workflow_sid'];
+  if (!$new_sid) {
+    return;
+  }
+
+  return array(
+    'force' => $form_state['input']['workflow_force'],
+    'form' => $form,
+    'form_state' => $form_state,
+  );
+}

+ 86 - 0
sites/all/modules/contrib/admin/workflow/workflow_vbo/actions/next.action.inc

@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * @file
+ * VBO action to modify entity values (properties and fields).
+ */
+
+/**
+ * Implements hook_action_info().
+ *
+ * Registers custom VBO actions as Drupal actions.
+ */
+function workflow_vbo_next_action_info() {
+  return array(
+    'workflow_vbo_next_state_action' => array(
+      'type' => 'entity',
+      'label' => t('Change workflow state of post to next state'),
+      'configurable' => FALSE,
+      'triggers' => array('any'),
+    ),
+  );
+}
+
+/**
+ * Implements a Drupal action. Move a node to the next state in the workflow.
+ *
+ * @param $entity
+ * @param array $context
+ */
+function workflow_vbo_next_state_action($entity, array $context) {
+  global $user;
+
+  // Get the entity type, entity and entity ID.
+  if (isset($context['entity_type'])) {
+    // In a VBO Action.
+    $entity_type = $context['entity_type'];
+  }
+  else {
+    // In an Advanced Action.
+    $entity_type = str_replace(array('_insert', '_update' , '_delete'), '', $context['hook']);
+  }
+  // Change the state of latest revision, not current revision.
+  if (isset($context[$entity_type])) {
+    $entity = $context[$entity_type];
+  }
+  elseif (!isset($entity)) {
+    $entity = $context['node'];
+  }
+  // In 'after saving new content', the node is already saved. Avoid second insert.
+  // Todo: clone?
+  unset($entity->is_new);
+
+  list($entity_id, , $entity_bundle) = entity_extract_ids($entity_type, $entity);
+  if (!$entity_id) {
+    watchdog('workflow_vbo', 'Unable to get current entity ID - entity is not yet saved.');
+    return;
+  }
+
+  // Get the current State Id. Also, $field_name will be set magically, by reference.
+  $field_name = NULL;
+  $current_sid = workflow_node_current_state($entity, $entity_type, $field_name);
+  // Get the Comment. It is empty.
+  $comment = '';
+  // Only 'normal' state transitions are valid.
+  $force = FALSE;
+
+  // Get the node's new State Id (which is the next available state).
+  $workflow = workflow_get_workflows_by_type($entity_bundle, $entity_type);
+  if (!$workflow) {
+    watchdog('workflow_vbo', 'Unable to get current workflow of entity %id.',
+      array('%id' => $entity_id));
+    return;
+  }
+
+  $new_sid = $workflow->getNextSid($entity_type, $entity, $field_name, $user, $force);
+  if (!$new_sid) {
+    watchdog('workflow_vbo', 'Unable to get current workflow state of entity %id.',
+      array('%id' => $entity_id));
+    return;
+  }
+
+  // Fire the transition.
+  $transition = new WorkflowTransition();
+  $transition->setValues($entity_type, $entity, $field_name, $current_sid, $new_sid, $user->uid, REQUEST_TIME, $comment);
+  workflow_execute_transition($entity_type, $entity, $field_name, $transition, $force);
+}

+ 10 - 6
sites/all/modules/contrib/admin/workflow/workflow_vbo/workflow_vbo.info

@@ -1,12 +1,16 @@
-name = Workflow VBO
-description = Provides workflow actions for VBO.
+name = Workflow Actions
+description = "Provides actions that can be associated to triggers, or
+               used as VBO-action. Provided actions are 'set to next state'
+               and 'set to specific state'."
+
 dependencies[] = workflow
-dependencies[] = views_bulk_operations
+
 package = Workflow
 core = 7.x
-; Information added by drupal.org packaging script on 2013-07-04
-version = "7.x-1.2"
+
+; Information added by Drupal.org packaging script on 2017-01-15
+version = "7.x-2.9+10-dev"
 core = "7.x"
 project = "workflow"
-datestamp = "1372980654"
+datestamp = "1484510888"
 

+ 13 - 129
sites/all/modules/contrib/admin/workflow/workflow_vbo/workflow_vbo.module

@@ -1,142 +1,26 @@
 <?php
 /**
  * @file
- * Provide workflow actions for VBO.
- * Split out from workflow_actions.
+ * Provides actions that can be associated to triggers, or used as VBO-action.
+ *
+ * N.B. This module's name is incorrect. It is not dependent on VBO.
+ *
+ * Each action is defined in its own file.
+ * @see https://drupal.org/node/2052067
  */
 
 /**
  * Implements hook_action_info().
  */
 function workflow_vbo_action_info() {
-  return array(
-    'workflow_vbo_next_state_action' => array(
-      'type' => 'node',
-      'label' => t('Change workflow state of post to next state'),
-      'configurable' => FALSE,
-      'triggers' => array('any'),
-      ),
-
-    'workflow_vbo_given_state_action' => array(
-      'type' => 'node',
-      'label' => t('Change workflow state of post to new state'),
-      'configurable' => TRUE,
-      'triggers' => array('any'),
-      ),
-    );
-}
-
-/**
- * Implements a Drupal action. Move a node to the next state in the workfow.
- */
-function workflow_vbo_next_state_action($node, $context) {
-  // If this action is being fired because it's attached to a workflow transition
-  // then the node's new state (now its current state) should be in $node->workflow
-  // because that is where the value from the workflow form field is stored;
-  // otherwise the current state is placed in $node->workflow by our nodeapi load.
-  if (!isset($node->nid)) {
-    watchdog('workflow_vbo', 'Unable to get current node id state of node - node is not yet saved.');
-    return;
-  }
-  if (!isset($node->workflow)) {
-    watchdog('workflow_vbo', 'Unable to get current workflow state of node %nid.',
-      array('%nid' => $node->nid));
-    return;
-  }
-
-  $current_state = $node->workflow;
-  $new_state = $current_state;
-
-  // Get the node's new state.
-  $choices = workflow_field_choices($node);
-  foreach ($choices as $sid => $name) {
-    if (isset($flag)) {
-      $new_state = $sid;
-      $new_state_name = $name;
-      break;
-    }
-    if ($sid == $current_state) {
-      $flag = TRUE;
-    }
-  }
-
-  // Fire the transition.
-  workflow_execute_transition($node, $new_state);
-}
-
-/**
- * Implements a Drupal action. Move a node to a specified state.
- */
-function workflow_vbo_given_state_action($node, $context) {
-  global $user;
-  if (!isset($node->nid)) {
-    watchdog('workflow_vbo', 'Unable to get current node id state of node - node is not yet saved.');
-    return;
-  }
+  $actions = array();
 
-  $comment = t($context['workflow_comment'], array(
-      '%title' => check_plain($node->title), 
-      '%state' => check_plain($context['state_name']),
-      '%user' => theme('username', array('account' => $user)),
-      ));
+  $path = drupal_get_path('module', 'workflow_vbo') . '/actions/';
+  include_once $path . 'given.action.inc';
+  include_once $path . 'next.action.inc';
 
-  workflow_execute_transition($node, $context['target_sid'], $comment, $context['force']);
-}
-
-/**
- * Configuration form for "Change workflow state of post to new state" action.
- *
- * @see workflow_vbo_given_state_action()
- */
-function workflow_vbo_given_state_action_form($context) {
-  $previous_workflow = '';
-  $options = array();
-
-  // Get all states, only where active.
-  foreach (workflow_get_workflow_states(array('status' => 1)) as $data) {
-    $options[$data->name][$data->sid] = check_plain($data->state);
-  }
-
-  $form['target_sid'] = array(
-    '#type' => 'select',
-    '#title' => t('Target state'),
-    '#description' => t('Please select that state that should be assigned when this action runs.'),
-    '#default_value' => isset($context['target_sid']) ? $context['target_sid'] : '',
-    '#options' => $options,
-    );
+  $actions += workflow_vbo_given_action_info();
+  $actions += workflow_vbo_next_action_info();
 
-  $form['force'] = array(
-    '#type' => 'checkbox',
-    '#title' => t('Force transition'),
-    '#description' => t('If this box is checked, the new state will be assigned even if workflow ' .
-      'permissions disallow it.'),
-    '#default_value' => isset($context['force']) ? $context['force'] : '',
-    );
-
-  $form['workflow_comment'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Message'),
-    '#description' => t('This message will be written into the workflow history log when the action ' .
-      'runs. You may include the following variables: %state, %title, %user'),
-    '#default_value' => isset($context['workflow_history']) ? $context['workflow_history'] : t('Action set %title to %state by %user.'),
-    );
-
-  return $form;
-}
-
-/**
- * Submit handler for "Change workflow state of post to new state" action
- * configuration form.
- *
- * @see workflow_vbo_given_state_action_form()
- */
-function workflow_vbo_given_state_action_submit($form_id, $form_state) {
-  if ($state = workflow_get_workflow_states_by_sid($form_state['values']['target_sid'])) {
-    return array(
-      'target_sid' => $form_state['values']['target_sid'],
-      'state_name' => check_plain($state->state),
-      'force' => $form_state['values']['force'],
-      'workflow_comment' => $form_state['values']['workflow_comment'],
-      );
-  }
+  return $actions;
 }

+ 32 - 0
sites/all/modules/contrib/admin/workflow/workflow_views/handlers/workflow_views_handler_argument_state.inc

@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Provide views argument handler for workflow.module.
+ */
+
+/**
+ * Argument handler to accept a node type.
+ */
+class views_handler_argument_workflow_state extends views_handler_argument {
+  function construct() {
+    parent::construct('type');
+  }
+
+  /**
+   * Overrides the behavior of summary_name().
+   *
+   * Gets the user-friendly version of the workflow state.
+   */
+  function summary_name($data) {
+    return workflow_get_sid_label($data->{$this->name_alias});
+  }
+
+  /**
+   * Overrides the behavior of title().
+   * Get the user-friendly version of the workflow state.
+   */
+  function title() {
+    return workflow_get_sid_label($this->argument);
+  }
+}

+ 49 - 0
sites/all/modules/contrib/admin/workflow/workflow_views/handlers/workflow_views_handler_field_comment_link_edit.inc

@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * @file
+ * Field handler to present a link to edit a workflow log comment.
+ */
+
+class workflow_views_handler_field_comment_link_edit extends views_handler_field {
+
+  function construct() {
+    parent::construct();
+    $this->additional_fields['hid'] = 'hid';
+  }
+
+  function option_definition() {
+    $options = parent::option_definition();
+    $options['text'] = array(
+      'default' => '',
+      'translatable' => TRUE,
+    );
+    return $options;
+  }
+
+  function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
+    $form['text'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Text to display'),
+      '#default_value' => $this->options['text'],
+    );
+  }
+
+  function query() {
+    $this->ensure_my_table();
+    $this->add_additional_fields();
+  }
+
+  function render($values) {
+    if (!user_access('edit workflow comment')) {
+      return;
+    }
+    if (!$hid = $values->{$this->aliases['hid']}) {
+      // No link if entity has no history.
+      return;
+    }
+    $text = empty($this->options['text']) ? t('edit comment') : $this->options['text'];
+    return l($text, "workflow_transition/$hid/edit", array('query' => drupal_get_destination()));
+  }
+}

+ 31 - 0
sites/all/modules/contrib/admin/workflow/workflow_views/handlers/workflow_views_handler_field_node_link_workflow.inc

@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Definition of workflow_views_handler_field_node_link_workflow.
+ */
+
+/**
+ * Field handler to present a link 'node workflow form'.
+ *
+ * @ingroup views_field_handlers
+ */
+class workflow_views_handler_field_node_link_workflow extends views_handler_field_node_link {
+  /**
+   * Renders the link.
+   */
+  function render_link($node, $values) {
+    // Ensure user has access to edit this node.
+    $entity_type = $this->entity_type;
+    // if (!node_access('update', $node)) {
+    if (!workflow_tab_access($entity_type, $node)) {
+      return;
+    }
+    $this->options['alter']['make_link'] = TRUE;
+    $this->options['alter']['path'] = "node/$node->nid/workflow"; // @todo: add support for other entity types.
+    $this->options['alter']['query'] = drupal_get_destination();
+
+    $text = !empty($this->options['text']) ? $this->options['text'] : t('change state');
+    return $text;
+  }
+}

+ 46 - 0
sites/all/modules/contrib/admin/workflow/workflow_views/handlers/workflow_views_handler_field_sid.inc

@@ -0,0 +1,46 @@
+<?php
+/**
+ * @file
+ * Provide views field handler for workflow.module.
+ */
+
+/**
+ * Field handler to provide simple status name or renderer.
+ */
+class workflow_views_handler_field_sid extends views_handler_field {
+
+  function option_definition() {
+    $options = parent::option_definition();
+    $options['value'] = array('default' => FALSE, 'bool' => TRUE);
+    return $options;
+  }
+
+  function options_form(&$form, &$form_state) {
+    $form['value'] = array(
+      '#title' => t('Display value'),
+      '#description' => t('Determines how the state will be displayed.'),
+      '#type' => 'select',
+      '#options' => array(
+        '0' => t('State name'),
+        '1' => t('State value'), // Keep this value for backwards compatibility.
+        'count' => t('Count number of entities with this state'),
+      ),
+      '#default_value' => $this->options['value'],
+    );
+    parent::options_form($form, $form_state);
+  }
+
+  function render($values) {
+    $sid = $values->{$this->field_alias};
+    if ($this->options['value'] == '1') {
+      return (empty($sid)) ? NULL : $sid;
+    }
+    elseif ($this->options['value'] == 'count') {
+      $state = workflow_state_load_single($sid);
+      return (empty($sid)) ? 0 : $state->count();
+    }
+    else {
+      return workflow_get_sid_label($sid);
+    }
+  }
+}

Some files were not shown because too many files changed in this diff