updated workflow module to last dev
This commit is contained in:
Executable → Regular
+559
-1
@@ -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 #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 #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.
|
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.
|
||||||
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.
|
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 #1542188 by justanothermark: Fix workflow display.
|
||||||
Issue #2027507 by NancyDru: Fix sorting issue on history page.
|
Issue #2027507 by NancyDru: Fix sorting issue on history page.
|
||||||
Issue #1993408 by NancyDru: Sysid not always set correctly.
|
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
|
||||||
|
|||||||
Executable → Regular
Executable → Regular
+73
-22
@@ -7,32 +7,40 @@ Maintainers: Mark Fredrickson <mark.m.fredrickson at gmail dot com>
|
|||||||
John VanDyk drupal.org/user/2375
|
John VanDyk drupal.org/user/2375
|
||||||
Bastlynn http://drupal.org/user/275249
|
Bastlynn http://drupal.org/user/275249
|
||||||
Nancy Wichmann (NancyDru) http://drupal.org/user/101412
|
Nancy Wichmann (NancyDru) http://drupal.org/user/101412
|
||||||
|
John Voskuilen (johnv) http://drupal.org/user/591042
|
||||||
Drupal: 7
|
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
|
The workflow module enables you to create arbitrary workflows in
|
||||||
Drupal and associate them with node types.
|
Drupal and associate them with node types.
|
||||||
|
|
||||||
Workflows are made up of workflow states.
|
Workflows are made up of workflow states.
|
||||||
|
|
||||||
Moving from one state to another is called a transition.
|
Moving from one state to another is called a transition.
|
||||||
|
Actions can be associated with transitions.
|
||||||
Actions are associated with transitions (actions.module was used
|
Tokens are supported when modules Token and Entity_tokens are enabled.
|
||||||
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.
|
|
||||||
|
|
||||||
********************************************************************
|
********************************************************************
|
||||||
INSTALLATION:
|
INSTALLATION
|
||||||
|
|
||||||
1. Place the entire workflow directory into your Drupal
|
1. Place the entire workflow directory into your Drupal
|
||||||
sites/all/modules directory (or appropriate alternative).
|
sites/all/modules directory (or appropriate alternative).
|
||||||
|
|
||||||
2. Enable the workflow module by navigating to:
|
2. Enable the workflow module by navigating to:
|
||||||
|
|
||||||
Administer > Site building > Modules
|
Administration » Modules
|
||||||
|
|
||||||
Enabling the workflow module will create the necessary database
|
Enabling the workflow module will create the necessary database
|
||||||
tables for you.
|
tables for you.
|
||||||
@@ -45,17 +53,16 @@ INSTALLATION:
|
|||||||
to configure workflows (usually a bad idea), they must be given
|
to configure workflows (usually a bad idea), they must be given
|
||||||
the "administer workflow" access permission:
|
the "administer workflow" access permission:
|
||||||
|
|
||||||
Administer > User management > Permissions
|
Administration » People » Permissions
|
||||||
|
|
||||||
When the module is enabled and the user has the "administer
|
When the module is enabled and the user has the "administer
|
||||||
workflow" permission, a "Workflow" menu should appear in the
|
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
|
You may also grant other Permissions to user roles.
|
||||||
the "schedule workflow transitions" permission to allow transitions.
|
|
||||||
|
|
||||||
********************************************************************
|
********************************************************************
|
||||||
GETTING STARTED:
|
GETTING STARTED: SETTING UP A WORKFLOW
|
||||||
|
|
||||||
Let's create a new workflow. Click on Administer -> Configuration ->
|
Let's create a new workflow. Click on Administer -> Configuration ->
|
||||||
Workflow -> Workflow and click on the "Add workflow" tab.
|
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.
|
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
|
Now we could add an action (previously configured using the trigger
|
||||||
module). Click on the Actions link above
|
module). Click on the Actions link above your workflow. Add the action
|
||||||
your workflow. Add the action to the transition.
|
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
|
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
|
is no sign of a workflow interface here, don't panic. The interface
|
||||||
@@ -111,3 +126,39 @@ Changing the state to "done" and clicking Submit will fire the action
|
|||||||
you set up earlier.
|
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
|
||||||
|
|||||||
Executable → Regular
+1
-1
@@ -9,7 +9,7 @@ CONTENTS OF THIS FILE
|
|||||||
UPDATE FROM DRUPAL 6 TO DRUPAL 7 INSTRUCTIONS
|
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:
|
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:
|
||||||
|
|
||||||
|
|||||||
@@ -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,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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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,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 {
|
||||||
|
}
|
||||||
@@ -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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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) {
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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.'));
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
||||||
Executable → Regular
+250
-23
@@ -7,57 +7,284 @@
|
|||||||
/**
|
/**
|
||||||
* Implements hook_workflow().
|
* Implements hook_workflow().
|
||||||
*
|
*
|
||||||
* @param $op
|
* NOTE: This hook may reside in the implementing module
|
||||||
* The current workflow operation: 'transition permitted', 'transition pre' or 'transition post'.
|
* or in a module.workflow.inc file.
|
||||||
* @param $old_state
|
*
|
||||||
* The state ID of the current state.
|
* @param string $op
|
||||||
* @param $new_state
|
* 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.
|
* The state ID of the new state.
|
||||||
* @param $node
|
* @param object $entity
|
||||||
* The node whose workflow state is changing.
|
* The entity whose workflow state is changing.
|
||||||
* @param $force
|
* @param bool $force
|
||||||
* The caller indicated that the transition should be forced. (bool).
|
* The caller indicated that the transition should be forced. (bool).
|
||||||
* This is only available on the "pre" and "post" calls.
|
* 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) {
|
switch ($op) {
|
||||||
case 'transition permitted':
|
case 'transition permitted':
|
||||||
// The workflow module does nothing during this operation.
|
// This is called in the following situations:
|
||||||
// This operation occurs when the list of available transitions
|
// 1. when building a workflow widget with list of available transitions;
|
||||||
// is built. Your module's implementation could return FALSE
|
// 2. when executing a transition, just before the 'transition pre';
|
||||||
// here and disallow the presentation of the choice.
|
// 3. when showing a 'revert state' link in a Views display.
|
||||||
break;
|
// 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':
|
case 'transition pre':
|
||||||
// The workflow module does nothing during this operation.
|
// The workflow module does nothing during this operation.
|
||||||
// But your module's implementation of the workflow hook could
|
// Implement this hook if you need to change/do something BEFORE anything
|
||||||
// return FALSE here and veto the transition.
|
// is saved to the database.
|
||||||
|
// If you return FALSE here, you will veto the transition.
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'transition post':
|
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;
|
break;
|
||||||
|
|
||||||
case 'transition delete':
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_workflow_history_alter().
|
* 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.
|
* The current workflow history information as an array.
|
||||||
* 'old_sid' - The state ID of the previous state.
|
* 'old_sid' - The state ID of the previous state.
|
||||||
* 'old_state_name' - The state name of the previous state.
|
* 'old_state_name' - The state name of the previous state.
|
||||||
* 'sid' - The state ID of the current state.
|
* 'sid' - The state ID of the current state.
|
||||||
* 'state_name' - The state name of the current state.
|
* 'state_name' - The state name of the current state.
|
||||||
* 'history' - The row from the workflow_node_history table.
|
* 'history' - The row from the workflow_node_history table.
|
||||||
*
|
* 'transition' - a WorkflowTransition object, containing all of the above.
|
||||||
* If you want to add additional data, such as an operation link,
|
|
||||||
* place it in the 'extra' value.
|
|
||||||
*/
|
*/
|
||||||
function hook_workflow_history_alter(&$variables) {
|
function hook_workflow_history_alter(array &$variables) {
|
||||||
// The Worflow module does nothing with this hook.
|
// The Workflow module does nothing with this hook.
|
||||||
// For an example implementation, see the Workflow Revert add-on.
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
Executable → Regular
+216
-240
@@ -2,269 +2,245 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @file
|
* @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');
|
define('WORKFLOW_FEATURES_AUTHOR_NAME', 'workflow_features_author_name');
|
||||||
|
|
||||||
/**
|
// Even if workflow Node is not enabled, Features may use Node API's type_maps.
|
||||||
* Workflows are a **faux-exportable** component.
|
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 = '') {
|
class WorkflowFeaturesController extends EntityDefaultFeaturesController {
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $export;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_features_export_render().
|
* Generates the result for hook_features_export().
|
||||||
*/
|
*/
|
||||||
function workflow_features_export_render($module, $data) {
|
public function export($data, &$export, $module_name = '') {
|
||||||
$translatables = $code = array();
|
$pipe = parent::export($data, $export, $module_name);
|
||||||
$code[] = ' $workflows = array();';
|
|
||||||
$code[] = '';
|
|
||||||
|
|
||||||
$workflows = workflow_get_workflows();
|
foreach ($data as $workflow_name) {
|
||||||
foreach ($data as $name) {
|
if ($workflow = workflow_load_by_name($workflow_name)) {
|
||||||
if ($workflow = workflow_get_workflows_full_object($name)) {
|
// Add dependency on workflow_node.
|
||||||
unset($workflow->wid);
|
if (count($workflow->getTypeMap())) {
|
||||||
$workflow_export = features_var_export($workflow, ' ');
|
$export['dependencies']['workflownode'] = 'workflownode';
|
||||||
$workflow_identifier = features_var_export($workflow->name);
|
}
|
||||||
$code[] = " // Exported workflow: $name";
|
// Add dependency on workflow_field.
|
||||||
$code[] = " \$workflows[{$workflow_identifier}] = {$workflow_export};";
|
$export['features']['Workflow'][$workflow_name] = $workflow_name;
|
||||||
$code[] = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$code[] = ' return $workflows;';
|
|
||||||
$code = implode("\n", $code);
|
|
||||||
return array('workflow_default_workflows' => $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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
workflow_update_workflows($workflow, FALSE);
|
|
||||||
|
|
||||||
// Cancel out if workflow failed to save.
|
|
||||||
if (!isset($workflow->wid) || empty($workflow->wid)) {
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we need to add data to the export for each type map, an array of sub-objects.
|
return $pipe;
|
||||||
// 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;
|
/**
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
$hook = isset($this->info['export']['default hook']) ? $this->info['export']['default hook'] : 'default_' . $this->type;
|
||||||
|
|
||||||
|
return array($hook => $code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "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);
|
||||||
|
|
||||||
|
$named_states[$name] = $state;
|
||||||
|
}
|
||||||
|
ksort($named_states);
|
||||||
|
|
||||||
|
// Identify states by machine name.
|
||||||
|
$workflow->states = $named_states;
|
||||||
|
|
||||||
|
return $sid_to_name_map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "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);
|
||||||
|
|
||||||
|
// Identify transitions by new machine name.
|
||||||
|
$workflow->transitions = $named_transitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 $workflow;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internally cache the user roles as core doesn't.
|
* Implements hook_features_COMPONENT_alter().
|
||||||
|
*
|
||||||
|
* Adds the corresponding Workflow to the WorkflowField.
|
||||||
*/
|
*/
|
||||||
function _workflow_user_roles($reset = FALSE) {
|
function workflow_features_pipe_field_base_alter(&$pipe, $data, $export) {
|
||||||
$roles = &drupal_static(__FUNCTION__);
|
if (!empty($data)) {
|
||||||
if ($reset || !isset($roles)) {
|
foreach ($data as $field_name) {
|
||||||
$roles = user_roles();
|
// $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 $roles;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translates a role string to RIDs for importing.
|
* Implements hook_features_api_alter().
|
||||||
*
|
*
|
||||||
* @param $role_string
|
* Ensures Workflow always fires last during rebuild, to ensure that roles
|
||||||
* A string of roles or fake 'author' role.
|
* referenced by workflows to be loaded-in when features contain roles.
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* A string of RIDs seperated by commas.
|
|
||||||
*/
|
*/
|
||||||
function _workflow_roles_to_rids($role_string) {
|
function workflow_features_api_alter(array &$components) {
|
||||||
$roles = _workflow_user_roles();
|
// FIXME: Why is Workflow the only features provider with an uppercase component name?
|
||||||
$rid_array = array();
|
$component_name = 'Workflow';
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return implode(',', $rid_array);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (isset($components[$component_name])) {
|
||||||
* Translates a string of rids to role names for exporting.
|
$setting = $components[$component_name];
|
||||||
*
|
unset($components[$component_name]);
|
||||||
* @param $rid_string
|
$components[$component_name] = $setting;
|
||||||
* A string of rids or fake 'author' role.
|
}
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* A string of role names seperated by commas.
|
|
||||||
*/
|
|
||||||
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, ',');
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable → Regular
+20
-6
@@ -1,12 +1,26 @@
|
|||||||
name = Workflow
|
name = Workflow API
|
||||||
description = "Allows the creation and assignment of arbitrary workflows to node types."
|
description = Workflow API. (Enable Workflow Node or Workflow Field to add arbitrary workflows to entities.)
|
||||||
package = Workflow
|
package = Workflow
|
||||||
core = 7.x
|
core = 7.x
|
||||||
files[] = workflow.pages.inc
|
|
||||||
|
|
||||||
; Information added by drupal.org packaging script on 2013-07-04
|
dependencies[] = entity (>7.x-1.5)
|
||||||
version = "7.x-1.2"
|
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"
|
core = "7.x"
|
||||||
project = "workflow"
|
project = "workflow"
|
||||||
datestamp = "1372980654"
|
datestamp = "1484510888"
|
||||||
|
|
||||||
|
|||||||
Executable → Regular
+481
-44
@@ -3,13 +3,21 @@
|
|||||||
/**
|
/**
|
||||||
* @file
|
* @file
|
||||||
* Install, update and uninstall functions for the workflow module.
|
* 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().
|
* Implements hook_schema().
|
||||||
*/
|
*/
|
||||||
@@ -31,29 +89,54 @@ function workflow_schema() {
|
|||||||
'description' => 'Workflows',
|
'description' => 'Workflows',
|
||||||
'fields' => array(
|
'fields' => array(
|
||||||
'wid' => array(
|
'wid' => array(
|
||||||
'description' => 'The primary identifier for a node.',
|
'description' => 'The primary identifier for a workflow.',
|
||||||
'type' => 'serial',
|
'type' => 'serial',
|
||||||
'not null' => TRUE
|
'not null' => TRUE,
|
||||||
),
|
),
|
||||||
'name' => array(
|
'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',
|
'type' => 'varchar',
|
||||||
'length' => '255',
|
'length' => '255',
|
||||||
'not null' => TRUE,
|
'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(
|
'tab_roles' => array(
|
||||||
'description' => 'The role IDs that can access the workflow tabs on node pages.',
|
'description' => 'The role IDs that can access the workflow tabs on node pages.',
|
||||||
'type' => 'varchar',
|
'type' => 'varchar',
|
||||||
'length' => '60',
|
'length' => '255',
|
||||||
'not null' => TRUE,
|
'not null' => TRUE,
|
||||||
'default' => ''
|
'default' => '',
|
||||||
|
'serialize' => TRUE,
|
||||||
),
|
),
|
||||||
'options' => array(
|
'options' => array(
|
||||||
'description' => 'Additional settings for the workflow.',
|
'description' => 'Additional settings for the workflow.',
|
||||||
'type' => 'text',
|
'type' => 'text',
|
||||||
'size' => 'big',
|
'size' => 'big',
|
||||||
'not null' => FALSE
|
'not null' => FALSE,
|
||||||
|
'serialize' => TRUE,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'primary key' => array('wid'),
|
'primary key' => array('wid'),
|
||||||
@@ -66,7 +149,7 @@ function workflow_schema() {
|
|||||||
'type' => 'varchar',
|
'type' => 'varchar',
|
||||||
'length' => '255',
|
'length' => '255',
|
||||||
'not null' => TRUE,
|
'not null' => TRUE,
|
||||||
'default' => ''
|
'default' => '',
|
||||||
),
|
),
|
||||||
'wid' => array(
|
'wid' => array(
|
||||||
'description' => 'The {workflows}.wid this record affects.',
|
'description' => 'The {workflows}.wid this record affects.',
|
||||||
@@ -74,7 +157,7 @@ function workflow_schema() {
|
|||||||
'unsigned' => TRUE,
|
'unsigned' => TRUE,
|
||||||
'not null' => TRUE,
|
'not null' => TRUE,
|
||||||
'default' => 0,
|
'default' => 0,
|
||||||
'disp-width' => '10'
|
'disp-width' => '10',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'indexes' => array(
|
'indexes' => array(
|
||||||
@@ -86,7 +169,22 @@ function workflow_schema() {
|
|||||||
'tid' => array(
|
'tid' => array(
|
||||||
'description' => 'The primary identifier for a workflow transition.',
|
'description' => 'The primary identifier for a workflow transition.',
|
||||||
'type' => 'serial',
|
'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(
|
'sid' => array(
|
||||||
'description' => 'The {workflow_states}.sid start state.',
|
'description' => 'The {workflow_states}.sid start state.',
|
||||||
@@ -94,7 +192,7 @@ function workflow_schema() {
|
|||||||
'unsigned' => TRUE,
|
'unsigned' => TRUE,
|
||||||
'not null' => TRUE,
|
'not null' => TRUE,
|
||||||
'default' => 0,
|
'default' => 0,
|
||||||
'disp-width' => '10'
|
'disp-width' => '10',
|
||||||
),
|
),
|
||||||
'target_sid' => array(
|
'target_sid' => array(
|
||||||
'description' => 'The {workflow_states}.sid target state.',
|
'description' => 'The {workflow_states}.sid target state.',
|
||||||
@@ -102,13 +200,14 @@ function workflow_schema() {
|
|||||||
'unsigned' => TRUE,
|
'unsigned' => TRUE,
|
||||||
'not null' => TRUE,
|
'not null' => TRUE,
|
||||||
'default' => 0,
|
'default' => 0,
|
||||||
'disp-width' => '10'
|
'disp-width' => '10',
|
||||||
),
|
),
|
||||||
'roles' => array(
|
'roles' => array(
|
||||||
'description' => 'The {role}.sid that a user must have to perform transition.',
|
'description' => 'The {role}.sid that a user must have to perform transition.',
|
||||||
'type' => 'varchar',
|
'type' => 'varchar',
|
||||||
'length' => '255',
|
'length' => '255',
|
||||||
'not null' => FALSE,
|
'not null' => FALSE,
|
||||||
|
'serialize' => TRUE,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'primary key' => array('tid'),
|
'primary key' => array('tid'),
|
||||||
@@ -130,14 +229,22 @@ function workflow_schema() {
|
|||||||
'unsigned' => TRUE,
|
'unsigned' => TRUE,
|
||||||
'not null' => TRUE,
|
'not null' => TRUE,
|
||||||
'default' => 0,
|
'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(
|
'state' => array(
|
||||||
'description' => 'The name of the state.',
|
'description' => 'The human-readable name of this state.',
|
||||||
'type' => 'varchar',
|
'type' => 'varchar',
|
||||||
'length' => '255',
|
'length' => '255',
|
||||||
'not null' => TRUE,
|
'not null' => TRUE,
|
||||||
'default' => '',
|
'default' => '',
|
||||||
|
'translatable' => TRUE,
|
||||||
),
|
),
|
||||||
'weight' => array(
|
'weight' => array(
|
||||||
'description' => 'The weight (order) of the state.',
|
'description' => 'The weight (order) of the state.',
|
||||||
@@ -167,19 +274,52 @@ function workflow_schema() {
|
|||||||
'primary key' => array('sid'),
|
'primary key' => array('sid'),
|
||||||
'indexes' => array(
|
'indexes' => array(
|
||||||
'sysid' => array('sysid'),
|
'sysid' => array('sysid'),
|
||||||
'wid' => array('wid')
|
'wid' => array('wid'),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
$schema['workflow_scheduled_transition'] = array(
|
$schema['workflow_scheduled_transition'] = array(
|
||||||
'fields' => 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(
|
'nid' => array(
|
||||||
'description' => 'The {node}.nid this transition is scheduled for.',
|
'description' => 'The entity ID of the object this transition belongs to.',
|
||||||
'type' => 'int',
|
'type' => 'int',
|
||||||
'unsigned' => TRUE,
|
'unsigned' => TRUE,
|
||||||
'not null' => TRUE,
|
'not null' => TRUE,
|
||||||
'default' => 0,
|
'default' => 0,
|
||||||
'disp-width' => '10',
|
'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(
|
'old_sid' => array(
|
||||||
'description' => 'The {workflow_states}.sid this state starts at.',
|
'description' => 'The {workflow_states}.sid this state starts at.',
|
||||||
'type' => 'int',
|
'type' => 'int',
|
||||||
@@ -219,8 +359,10 @@ function workflow_schema() {
|
|||||||
'not null' => FALSE,
|
'not null' => FALSE,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
'primary key' => array('tid'),
|
||||||
'indexes' => array(
|
'indexes' => array(
|
||||||
'nid' => array('nid')
|
'entity_type' => array('entity_type'),
|
||||||
|
'entity_id' => array('entity_type', 'nid'),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
$schema['workflow_node_history'] = array(
|
$schema['workflow_node_history'] = array(
|
||||||
@@ -228,7 +370,14 @@ function workflow_schema() {
|
|||||||
'hid' => array(
|
'hid' => array(
|
||||||
'description' => 'The unique ID for this record.',
|
'description' => 'The unique ID for this record.',
|
||||||
'type' => 'serial',
|
'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(
|
'nid' => array(
|
||||||
'description' => 'The {node}.nid this record is for.',
|
'description' => 'The {node}.nid this record is for.',
|
||||||
@@ -238,12 +387,40 @@ function workflow_schema() {
|
|||||||
'default' => 0,
|
'default' => 0,
|
||||||
'disp-width' => '10',
|
'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(
|
'old_sid' => array(
|
||||||
'description' => 'The {workflow_states}.sid this transition started as.',
|
'description' => 'The {workflow_states}.sid this transition started as.',
|
||||||
'type' => 'int',
|
'type' => 'int',
|
||||||
'unsigned' => TRUE,
|
'unsigned' => TRUE,
|
||||||
'not null' => TRUE, '
|
'not null' => TRUE,
|
||||||
default' => 0,
|
'default' => 0,
|
||||||
'disp-width' => '10',
|
'disp-width' => '10',
|
||||||
),
|
),
|
||||||
'sid' => array(
|
'sid' => array(
|
||||||
@@ -279,7 +456,8 @@ function workflow_schema() {
|
|||||||
),
|
),
|
||||||
'primary key' => array('hid'),
|
'primary key' => array('hid'),
|
||||||
'indexes' => array(
|
'indexes' => array(
|
||||||
'nid' => array('nid', 'sid'),
|
'sid' => array('entity_type', 'nid', 'sid'),
|
||||||
|
'nid' => array('nid'),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
$schema['workflow_node'] = array(
|
$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() {
|
function workflow_update_7000() {
|
||||||
if (!db_index_exists('workflows', 'name')) {
|
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.
|
* Add userid to scheduled transition table.
|
||||||
*/
|
*/
|
||||||
@@ -377,3 +538,279 @@ function workflow_update_7002() {
|
|||||||
'initial' => 0,
|
'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'));
|
||||||
|
}
|
||||||
|
|||||||
Executable → Regular
Executable → Regular
+943
-1539
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable → Regular
+162
-171
@@ -2,81 +2,137 @@
|
|||||||
/**
|
/**
|
||||||
* @file
|
* @file
|
||||||
* Provide user interface for changing workflow state.
|
* 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.
|
* 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) {
|
function workflow_tab_page($entity_type, $entity = NULL) {
|
||||||
drupal_set_title($node->title);
|
drupal_set_title(entity_label($entity_type, $entity));
|
||||||
$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);
|
|
||||||
}
|
|
||||||
$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().
|
$form = array();
|
||||||
$current_state = theme('workflow_history_current_state', array('state_name' => $states[$current], 'state_system_name' => $state_system_names[$current], 'sid' => $current));
|
$field_name = NULL;
|
||||||
|
$workflow = NULL;
|
||||||
|
|
||||||
$output = theme('workflow_current_state', array('state' => $states[$current], 'state_system_name' => $state_system_names[$current], 'sid' => $current));
|
// 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);
|
||||||
|
|
||||||
// Show the form to allow state changing.
|
// Get the current sid. $field_name is updated with relevant value.
|
||||||
// But only if we are not at the terminal state.
|
$current_sid = workflow_node_current_state($entity, $entity_type, $field_name);
|
||||||
$choices = workflow_field_choices($node);
|
$current_state = workflow_state_load_single($current_sid);
|
||||||
if (count($choices) != 0 || count($choices) != 1 || $current != key($choices)) {
|
$workflow = $current_state->getWorkflow();
|
||||||
$form = drupal_get_form('workflow_tab_form', $node, $workflow->wid, $states, $current);
|
|
||||||
$output .= drupal_render($form);
|
// 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);
|
||||||
|
|
||||||
|
$output = drupal_render($form);
|
||||||
|
|
||||||
|
|
||||||
|
// Show the history table.
|
||||||
$rows = array();
|
$rows = array();
|
||||||
foreach (workflow_get_workflow_node_history_by_nid($node->nid) as $history) {
|
$current_themed = FALSE;
|
||||||
if ($history->sid == $current && !isset($deleted_states[$history->sid]) && !isset($current_themed)) {
|
$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.
|
// 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
|
// 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.
|
// of this node where the node was in this state do not need to be specially themed.
|
||||||
$current_themed = TRUE;
|
$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.
|
// 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;
|
$footer_needed = TRUE;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Regular state.
|
// Regular state.
|
||||||
$state_name = check_plain(t($states[$history->sid]));
|
$new_state_name = $label;
|
||||||
}
|
}
|
||||||
if (isset($deleted_states[$history->old_sid])) {
|
unset($new_state); // Not needed anymore.
|
||||||
$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));
|
|
||||||
|
$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 (!$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;
|
$footer_needed = TRUE;
|
||||||
}
|
}
|
||||||
elseif (isset($states[$history->old_sid])) {
|
|
||||||
$old_state_name = check_plain(t($states[$history->old_sid]));
|
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
$old_state_name = '*';
|
// Regular state.
|
||||||
|
$old_state_name = $label;
|
||||||
}
|
}
|
||||||
|
unset($old_state); // Not needed anymore.
|
||||||
|
|
||||||
$variables = array(
|
$variables = array(
|
||||||
'history' => $history,
|
'transition' => $history, // @todo D8: pass this WorkflowTransition as only variable. It contains everything.
|
||||||
'old_sid' => $history->old_sid,
|
'extra' => '',
|
||||||
'old_state_name' => $old_state_name,
|
|
||||||
'sid' => $history->sid,
|
'history' => $history, // @todo D8: remove, as this is the same as 'transition'.
|
||||||
'uid' => $history->uid,
|
'old_sid' => $history->old_sid, // @todo D8: remove this redundant property.
|
||||||
'state_name' => $state_name,
|
'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.
|
// Allow other modules to modify the row.
|
||||||
|
// $todo D8: pass only a $transition object.
|
||||||
drupal_alter('workflow_history', $variables);
|
drupal_alter('workflow_history', $variables);
|
||||||
|
|
||||||
$rows[] = theme('workflow_history_table_row', $variables);
|
$rows[] = theme('workflow_history_table_row', $variables);
|
||||||
@@ -87,16 +143,27 @@ function workflow_tab_page($node = NULL) {
|
|||||||
$last = count($rows) - 1;
|
$last = count($rows) - 1;
|
||||||
$rows[$last]['class'][] = 'last';
|
$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.
|
// Only display the table if there's anything in it.
|
||||||
if ($rows) {
|
if ($rows) {
|
||||||
$output .= theme('workflow_history_table', array('rows' => $rows, 'footer' => !empty($footer_needed)));
|
$variables = array(
|
||||||
$output .= theme('pager', array('tags' => variable_get('workflow_states_per_page', 20)));
|
'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;
|
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
|
* $old_state_name and $state_name must be run through check_plain(t()) prior
|
||||||
* to calling this theme function.
|
* to calling this theme function.
|
||||||
@@ -105,155 +172,79 @@ function theme_workflow_history_table_row($variables) {
|
|||||||
$row = array();
|
$row = array();
|
||||||
$old_state_name = $variables['old_state_name'];
|
$old_state_name = $variables['old_state_name'];
|
||||||
$state_name = $variables['state_name'];
|
$state_name = $variables['state_name'];
|
||||||
$history = $variables['history'];
|
$transition = $variables['transition'];
|
||||||
$account = user_load($variables['uid']);
|
|
||||||
$row = array(
|
$row = array(
|
||||||
'data' => 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' => $old_state_name, 'class' => array('previous-state-name')),
|
||||||
array('data' => $state_name, 'class' => array('state-name')),
|
array('data' => $state_name, 'class' => array('state-name')),
|
||||||
array('data' => theme('username', array('account' => $account)), 'class' => array('user-name')),
|
array('data' => theme('username', array('account' => $transition->getUser())), 'class' => array('user-name')),
|
||||||
array('data' => filter_xss($history->comment), 'class' => array('log-comment')),
|
array('data' => filter_xss($transition->comment), 'class' => array('log-comment')),
|
||||||
),
|
$variables['extra'],
|
||||||
|
),
|
||||||
'class' => array('workflow_history_row'),
|
'class' => array('workflow_history_row'),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!empty($variables['extra'])) {
|
|
||||||
$row['data'][] = $variables['extra'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $row;
|
return $row;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Theme entire workflow history table.
|
* Theme entire workflow history table.
|
||||||
*/
|
*/
|
||||||
function theme_workflow_history_table($variables) {
|
function theme_workflow_history_table($variables) {
|
||||||
|
$header = $variables['header'];
|
||||||
$rows = $variables['rows'];
|
$rows = $variables['rows'];
|
||||||
$footer = $variables['footer'];
|
$footer = $variables['footer'];
|
||||||
$headers = array(t('Date'), t('Old State'), t('New State'), t('By'), t('Comment'));
|
$entity = $variables['entity'];
|
||||||
$output = theme('table', array('header' => $headers, 'rows' => $rows, 'caption' => t('Workflow History')));
|
$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) {
|
if ($footer) {
|
||||||
$output .= t('*State is no longer available.');
|
$output .= MARK_STATE_IS_DELETED . ' ' . t('State is no longer available.');
|
||||||
}
|
}
|
||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Theme the current state in the workflow history table.
|
* 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) {
|
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.
|
* Theme a deleted state in the workflow history table.
|
||||||
|
*
|
||||||
|
* $state_name must be run through check_plain(t()) prior
|
||||||
|
* to calling this theme function.
|
||||||
*/
|
*/
|
||||||
function theme_workflow_deleted_state($variables) {
|
function theme_workflow_deleted_state($variables) {
|
||||||
return check_plain(t($variables['state_name'])) . '*';
|
return $variables['state_name'] . MARK_STATE_IS_DELETED;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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()
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
Executable → Regular
+390
-203
@@ -1,229 +1,416 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file
|
* @file
|
||||||
* Tokens hooks for Workflow module.
|
* 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 hook_tokens().
|
* 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';
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
*/
|
*/
|
||||||
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'];
|
* Adds a subtree to each WorkflowField.
|
||||||
|
*
|
||||||
|
* ATM we only generate tokens for the last transition of a field.
|
||||||
|
*/
|
||||||
|
function workflow_token_info_alter(&$data) {
|
||||||
|
|
||||||
// Get a list of all possible states for returning state names.
|
foreach ($data['tokens'] as $object => &$tokens) {
|
||||||
$states = workflow_get_workflow_states_all();
|
// 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',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if ($workflow = workflow_get_workflow_type_map_by_type($node->type)) {
|
// High-jack the fields (they do not have sub-tokens, yet).
|
||||||
// @TODO: If we ever move Workflow to allow M:M content types to workflows, this will need updating.
|
foreach ($tokens as &$token) {
|
||||||
if ($workflow = workflow_get_workflows_by_wid($workflow->wid)) {
|
// Caveat: the following algorithm is just a guess.
|
||||||
$wid = $workflow->wid;
|
if (isset($token['module']) && $token['module'] == 'token') {
|
||||||
$last_history = workflow_get_recent_node_history($node->nid);
|
if (isset($token['description']) && 0 == substr_compare($token['description'], 'Workflow', 0, 8)) {
|
||||||
|
$token['type'] = 'WorkflowTransition';
|
||||||
if (isset($node->workflow) && !isset($node->workflow_stamp)) {
|
$token['module'] = 'workflow';
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
$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 : '';
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $replacements;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_token_info().
|
* 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() {
|
function workflow_token_info() {
|
||||||
$type = array(
|
|
||||||
'name' => t('Workflow'),
|
|
||||||
'description' => t('Tokens related to workflows.'),
|
|
||||||
'needs-data' => 'node',
|
|
||||||
);
|
|
||||||
|
|
||||||
$tokens = array();
|
if (!module_exists('entity_token')) {
|
||||||
$tokens['workflow-name'] = array(
|
return 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',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return array(
|
/*
|
||||||
'types' => array('workflow' => $type),
|
* Token types.
|
||||||
'tokens' => array('workflow' => $tokens, 'node' => $tokens),
|
*/
|
||||||
|
$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 = 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];
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
$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.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the token replacement by correctly obeying the options.
|
||||||
|
*
|
||||||
|
* Taken from _entity_token_get_token().
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
|
|||||||
Executable → Regular
+62
-49
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @file
|
||||||
* Workflow_access records are a **faux-exportable** component.
|
* Workflow_access records are a **faux-exportable** component.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -33,37 +34,44 @@ SQL;
|
|||||||
*
|
*
|
||||||
* Provide a list of all workflows as selectable Feature components.
|
* Provide a list of all workflows as selectable Feature components.
|
||||||
*
|
*
|
||||||
* We'll make all workflow access records related to the selected workflows
|
* Generates the options to choose from.
|
||||||
* available for export.
|
* Using $wid as key. Result is used by hook_features_export().
|
||||||
*/
|
*/
|
||||||
function workflow_access_features_export_options() {
|
function workflow_access_features_export_options() {
|
||||||
$workflows = array();
|
$workflows = array();
|
||||||
foreach (workflow_get_workflows() as $workflow) {
|
foreach (workflow_load_multiple() as $workflow) {
|
||||||
$workflows[$workflow->name] = t('workflow access configuration for: ') . $workflow->name;
|
$name = $workflow->getName();
|
||||||
|
$workflows[$name] = t('workflow access configuration for: @label', array('@label' => $name));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $workflows;
|
return $workflows;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_features_export().
|
* Implements hook_features_export().
|
||||||
*
|
|
||||||
* Inform Features about dependencies.
|
|
||||||
*/
|
*/
|
||||||
function workflow_access_features_export($data, &$export, $module_name = '') {
|
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_access'] = 'workflow_access';
|
||||||
$export['dependencies']['workflow'] = 'workflow';
|
$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.
|
$roles = _workflow_access_get_affected_roles();
|
||||||
$pipe['workflow'][$component] = $component;
|
|
||||||
|
|
||||||
// And it needs the roles to which rights are granted.
|
foreach ($data as $workflow_name) {
|
||||||
foreach ($roles[$component] as $rname) {
|
if ($workflow = workflow_load_by_name($workflow_name)) {
|
||||||
$pipe['user_role'][$rname] = $rname;
|
|
||||||
|
$export['features']['workflow_access'][$workflow_name] = $workflow_name;
|
||||||
|
$export['features']['workflow'][$workflow_name] = $workflow_name;
|
||||||
|
|
||||||
|
// 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
|
// For each selected workflow, fetch all related workflow access records
|
||||||
// we want to put into code.
|
// we want to put into code.
|
||||||
foreach ($data as $workflow_name) {
|
foreach ($data as $workflow_name) {
|
||||||
$workflow = workflow_get_workflows_by_name($workflow_name);
|
$workflow = workflow_load_by_name($workflow_name);
|
||||||
if (!empty($workflow)) {
|
if ($workflow) {
|
||||||
$states = workflow_get_workflow_states_by_wid($workflow->wid);
|
$states = $workflow->getStates($all = TRUE);
|
||||||
if (!empty($states)) {
|
if ($states) {
|
||||||
$code[] = "\n \$workflows['$workflow_name'] = array();";
|
$code[] = "\n \$workflows['$workflow_name'] = array();";
|
||||||
foreach ($states as $state) {
|
foreach ($states as $state) {
|
||||||
$access_records = workflow_access_get_features_workflow_access_by_sid($state->sid);
|
$access_records = workflow_access_get_features_workflow_access_by_sid($state->sid);
|
||||||
if (!empty($access_records)) {
|
if (!empty($access_records)) {
|
||||||
$state_name = $state->state;
|
$state_name = $state->getName();
|
||||||
$code[] = " \$workflows['$workflow_name']['$state_name'] = array();";
|
$code[] = " \$workflows['$workflow_name']['$state_name'] = array();";
|
||||||
foreach ($access_records as $record) {
|
foreach ($access_records as $record) {
|
||||||
$rname = $record->rname;
|
$rname = $record->rname;
|
||||||
@@ -117,21 +125,28 @@ function workflow_access_features_export_render($module_name, $data, $export = N
|
|||||||
* into the workflow_access table.
|
* into the workflow_access table.
|
||||||
*/
|
*/
|
||||||
function workflow_access_features_rebuild($module) {
|
function workflow_access_features_rebuild($module) {
|
||||||
$workflow_access_records = module_invoke($module, 'workflow_access_features_default_settings');
|
// @see d.o. https://www.drupal.org/node/2472501
|
||||||
// retrieve the workflow ids
|
// $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();
|
$wids = array();
|
||||||
foreach ($workflow_access_records as $workflow_name => $state) {
|
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;
|
$wids[$workflow_name] = $workflow->wid;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($wids as $workflow_name => $wid) {
|
foreach ($wids as $workflow_name => $wid) {
|
||||||
$states = workflow_get_workflow_states_by_wid($wid);
|
$workflow = workflow_load_by_name($workflow_name);
|
||||||
foreach ($states as $state) {
|
if (!empty($workflow) && $states = $workflow->getStates($all = TRUE)) {
|
||||||
// Remove all workflow access records for states belonging to this
|
foreach ($states as $state) {
|
||||||
// workflow. We don't want lingering entries - we only want the ones we're
|
// Remove all workflow access records for states belonging to this
|
||||||
// about to insert.
|
// workflow. We don't want lingering entries - we only want the ones we're
|
||||||
workflow_access_delete_workflow_access_by_sid($state->sid);
|
// 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, ...)
|
// workflow_name[state_name][role_name] => array(grant_name => 0|1, ...)
|
||||||
foreach ($workflow_access_records as $workflow_name => $states) {
|
foreach ($workflow_access_records as $workflow_name => $states) {
|
||||||
foreach ($states as $state_name => $records) {
|
foreach ($states as $state_name => $records) {
|
||||||
$state = workflow_get_workflow_states_by_wid_state($wids[$workflow_name], $state_name);
|
if ($state = workflow_state_load_by_name($state_name, $wids[$workflow_name])) {
|
||||||
if (is_array($state)) {
|
foreach ($records as $rname => $record) {
|
||||||
$state = array_pop($state);
|
$record['sid'] = ($state) ? $state->sid : $state->sid;
|
||||||
}
|
if ($rname == WORKFLOW_FEATURES_AUTHOR_NAME) {
|
||||||
foreach ($records as $rname => $record) {
|
$record['rid'] = WORKFLOW_ROLE_AUTHOR_RID;
|
||||||
$record['sid'] = (is_array($state)) ? $state[0]->sid : $state->sid;
|
}
|
||||||
if ($rname == WORKFLOW_FEATURES_AUTHOR_NAME) {
|
else {
|
||||||
$record['rid'] = -1;
|
$role = user_role_load_by_name($rname);
|
||||||
|
$record['rid'] = $role->rid;
|
||||||
|
}
|
||||||
|
workflow_access_insert_workflow_access_by_sid($record);
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
$role = user_role_load_by_name($rname);
|
|
||||||
$record['rid'] = $role->rid;
|
|
||||||
}
|
|
||||||
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.
|
* Get workflow_access object like below by state id.
|
||||||
*
|
*
|
||||||
* array(
|
* Array(
|
||||||
* 'rname' => 'authenticated user',
|
* 'rname' => 'authenticated user',
|
||||||
* 'grant_view' => 1,
|
* 'grant_view' => 1,
|
||||||
* 'grant_update' => 0,
|
* 'grant_update' => 0,
|
||||||
* 'grant_delete' => 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.
|
* given sid.
|
||||||
*/
|
*/
|
||||||
function workflow_access_get_features_workflow_access_by_sid($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,
|
// Get all workflow access rules for a sid, where wa.rid is either a valid
|
||||||
// stands for the author
|
// role or -1, which stands for the author.
|
||||||
$sql = <<<SQL
|
$sql = <<<SQL
|
||||||
SELECT
|
SELECT
|
||||||
r.name as rname,
|
r.name as rname,
|
||||||
@@ -188,9 +201,9 @@ SELECT
|
|||||||
FROM {workflow_access} wa
|
FROM {workflow_access} wa
|
||||||
LEFT JOIN {role} r ON wa.rid = r.rid
|
LEFT JOIN {role} r ON wa.rid = r.rid
|
||||||
WHERE
|
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;
|
SQL;
|
||||||
$results = db_query($sql, array(':sid' => $sid));
|
$results = db_query($sql, array(':sid' => $sid, ':rid' => WORKFLOW_ROLE_AUTHOR_RID));
|
||||||
$records = $results->fetchAll();
|
$records = $results->fetchAll();
|
||||||
foreach ($records as $record) {
|
foreach ($records as $record) {
|
||||||
if (empty($record->rname)) {
|
if (empty($record->rname)) {
|
||||||
|
|||||||
Executable → Regular
+5
-4
@@ -1,11 +1,12 @@
|
|||||||
name = Workflow access
|
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
|
dependencies[] = workflow
|
||||||
package = Workflow
|
package = Workflow
|
||||||
core = 7.x
|
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"
|
core = "7.x"
|
||||||
project = "workflow"
|
project = "workflow"
|
||||||
datestamp = "1372980654"
|
datestamp = "1484510888"
|
||||||
|
|
||||||
|
|||||||
Executable → Regular
+36
-13
@@ -5,16 +5,6 @@
|
|||||||
* Workflow access installation.
|
* Workflow access installation.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements hook_install().
|
|
||||||
*/
|
|
||||||
function workflow_access_install() { }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements hook_uninstall().
|
|
||||||
*/
|
|
||||||
function workflow_access_uninstall() { }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_schema().
|
* Implements hook_schema().
|
||||||
*/
|
*/
|
||||||
@@ -23,13 +13,13 @@ function workflow_access_schema() {
|
|||||||
'description' => 'Workflow access tables',
|
'description' => 'Workflow access tables',
|
||||||
'fields' => array(
|
'fields' => array(
|
||||||
'sid' => array(
|
'sid' => array(
|
||||||
'type' => 'serial',
|
'type' => 'int',
|
||||||
|
'description' => 'The Workflow state ID.',
|
||||||
'not null' => TRUE,
|
'not null' => TRUE,
|
||||||
),
|
),
|
||||||
'rid' => array(
|
'rid' => array(
|
||||||
'type' => 'int',
|
'type' => 'int',
|
||||||
'not null' => TRUE,
|
'not null' => TRUE,
|
||||||
'default' => 0,
|
|
||||||
),
|
),
|
||||||
'grant_view' => array(
|
'grant_view' => array(
|
||||||
'type' => 'int',
|
'type' => 'int',
|
||||||
@@ -53,10 +43,43 @@ function workflow_access_schema() {
|
|||||||
'default' => 0,
|
'default' => 0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
'primary key' => array('sid', 'rid'),
|
||||||
'indexes' => array(
|
'indexes' => array(
|
||||||
'rid' => array('rid'),
|
'rid' => array('rid'),
|
||||||
'sid' => array('sid'),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return $schema;
|
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);
|
||||||
|
}
|
||||||
|
|||||||
Executable → Regular
+178
-265
@@ -1,26 +1,118 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file
|
* @file
|
||||||
* Provides node access permissions based on workflow states.
|
* Provides node access permissions based on workflow states.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_menu().
|
* Implements hook_menu().
|
||||||
|
*
|
||||||
|
* Uses pattern of EntityWorkflowUIController::hook_menu().
|
||||||
*/
|
*/
|
||||||
function workflow_access_menu() {
|
function workflow_access_menu() {
|
||||||
$items = array();
|
$items = array();
|
||||||
|
|
||||||
$items["admin/config/workflow/access/%workflow"] = array(
|
$path = 'admin/config/workflow/workflow';
|
||||||
'title' => 'Access',
|
$id_count = count(explode('/', $path));
|
||||||
|
|
||||||
|
$items[$path . '/access'] = array(
|
||||||
|
'title' => 'Access settings',
|
||||||
|
'file' => 'workflow_access.pages.inc',
|
||||||
'access arguments' => array('administer workflow'),
|
'access arguments' => array('administer workflow'),
|
||||||
'page callback' => 'drupal_get_form',
|
'page callback' => 'drupal_get_form',
|
||||||
'page arguments' => array('workflow_access_form', 4),
|
'page arguments' => array('workflow_access_priority_form'),
|
||||||
'type' => MENU_CALLBACK,
|
'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',
|
||||||
|
$id_count + 1,
|
||||||
|
$id_count + 2,
|
||||||
|
),
|
||||||
|
// 'type' => MENU_CALLBACK,
|
||||||
|
// 'type' => MENU_LOCAL_TASK,
|
||||||
|
);
|
||||||
|
|
||||||
return $items;
|
return $items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements hook_help().
|
||||||
|
*/
|
||||||
|
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 $help;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements hook_workflow_operations().
|
||||||
|
*
|
||||||
|
* Create action link for access form on EntityWorkflowUIController::overviewForm.
|
||||||
|
*/
|
||||||
|
function workflow_access_workflow_operations($op, $workflow = NULL, $state = NULL) {
|
||||||
|
switch ($op) {
|
||||||
|
case 'workflow':
|
||||||
|
$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/workflow/manage/$wid/access",
|
||||||
|
'attributes' => array('alt' => $alt, 'title' => $alt),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $actions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_node_grants().
|
* Implements hook_node_grants().
|
||||||
*
|
*
|
||||||
@@ -38,27 +130,94 @@ function workflow_access_node_grants($account, $op) {
|
|||||||
* Implements hook_node_access_records().
|
* Implements hook_node_access_records().
|
||||||
*
|
*
|
||||||
* Returns a list of grant records for the passed in node object.
|
* 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) {
|
function workflow_access_node_access_records($node) {
|
||||||
$grants = array();
|
$grants = array();
|
||||||
if ($state = workflow_get_workflow_node_by_nid($node->nid)) {
|
$workflow_transitions_added = FALSE;
|
||||||
$state = workflow_get_workflow_states_by_sid($state->sid);
|
|
||||||
foreach (workflow_access_get_workflow_access_by_sid($state->sid) as $grant) {
|
// Only relevant for content with Workflow.
|
||||||
$grants[] = array(
|
if (!isset($node->workflow_transitions)) {
|
||||||
'realm' => ($grant->rid == -1) ? 'workflow_access_owner' : 'workflow_access',
|
$node->workflow_transitions = array();
|
||||||
'gid' => ($grant->rid == -1) ? $node->uid : $grant->rid,
|
// Sometimes, a node is saved without going through workflow_transition_form.
|
||||||
'grant_view' => $grant->grant_view,
|
// E.g.,
|
||||||
'grant_update' => $grant->grant_update,
|
// - when saving a node with workflow_node programmatically with node_save();
|
||||||
'grant_delete' => $grant->grant_delete,
|
// - when changing a state on a node view page/history tab;
|
||||||
'priority' => variable_get('workflow_access_priority', 0),
|
// - 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;
|
||||||
|
}
|
||||||
|
elseif (isset($node->{$field_name}[LANGUAGE_NONE])) {
|
||||||
|
$old_sid = $new_sid = _workflow_get_sid_by_items($node->{$field_name}[LANGUAGE_NONE]);
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
$count = 0;
|
||||||
|
foreach ($node->workflow_transitions as $transition) {
|
||||||
|
// @todo: add support for +1 workflows per entity.
|
||||||
|
if ($count++ == 1 ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$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.
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($workflow_transitions_added == TRUE) {
|
||||||
|
unset($node->workflow_transitions);
|
||||||
|
}
|
||||||
|
|
||||||
return $grants;
|
return $grants;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_node_access_explain().
|
* Implements hook_node_access_explain().
|
||||||
|
*
|
||||||
* This is a Devel Node Access hook.
|
* This is a Devel Node Access hook.
|
||||||
*/
|
*/
|
||||||
function workflow_access_node_access_explain($row) {
|
function workflow_access_node_access_explain($row) {
|
||||||
@@ -67,6 +226,7 @@ function workflow_access_node_access_explain($row) {
|
|||||||
case 'workflow_access_owner':
|
case 'workflow_access_owner':
|
||||||
$interpretations[$row->gid] = t('Workflow access: author of the content may access');
|
$interpretations[$row->gid] = t('Workflow access: author of the content may access');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'workflow_access':
|
case 'workflow_access':
|
||||||
$roles = user_roles();
|
$roles = user_roles();
|
||||||
$interpretations[$row->gid] = t('Workflow access: %role may access', array('%role' => $roles[$row->gid]));
|
$interpretations[$row->gid] = t('Workflow access: %role may access', array('%role' => $roles[$row->gid]));
|
||||||
@@ -75,199 +235,6 @@ function workflow_access_node_access_explain($row) {
|
|||||||
return (!empty($interpretations[$row->gid]) ? $interpretations[$row->gid] : NULL);
|
return (!empty($interpretations[$row->gid]) ? $interpretations[$row->gid] : NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements hook_workflow_operations().
|
|
||||||
* Create action link for access form.
|
|
||||||
*/
|
|
||||||
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(
|
|
||||||
'workflow_access_form' => array(
|
|
||||||
'title' => t('Access'),
|
|
||||||
'href' => "admin/config/workflow/access/$workflow->wid",
|
|
||||||
'attributes' => array('alt' => $alt, 'title' => $alt),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return $actions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
|
||||||
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');
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
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' => 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';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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)) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
drupal_set_message(t('Workflow access permissions updated.'));
|
|
||||||
$form_state['redirect'] = 'admin/config/workflow/workflow/' . $form['#wid'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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, $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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DB functions - all DB interactions are isolated here to make for easy updating should our schema change.
|
* DB functions - all DB interactions are isolated here to make for easy updating should our schema change.
|
||||||
*/
|
*/
|
||||||
@@ -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) {
|
function workflow_access_delete_workflow_access_by_sid_rid($sid, $rid) {
|
||||||
db_delete('workflow_access')->condition('sid', $sid)->condition('rid', $rid)->execute();
|
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);
|
workflow_access_delete_workflow_access_by_sid_rid($data->sid, $data->rid);
|
||||||
drupal_write_record('workflow_access', $data);
|
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,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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'];
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,7 +22,7 @@ the content type.
|
|||||||
|
|
||||||
So: KEEP YOUR CONTENT TYPE NAMES SHORT.
|
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.
|
you have trouble seeing your actions check your name lengths.
|
||||||
|
|
||||||
See further discussion at:
|
See further discussion at:
|
||||||
|
|||||||
Executable → Regular
+8
-5
@@ -1,12 +1,15 @@
|
|||||||
name = Workflow actions and triggers
|
name = Workflow Trigger
|
||||||
description = Provides actions and triggers for workflows.
|
description = Enables actions to be fired upon a Workflow State change.
|
||||||
|
|
||||||
dependencies[] = workflow
|
dependencies[] = workflow
|
||||||
dependencies[] = trigger
|
dependencies[] = trigger
|
||||||
|
|
||||||
package = Workflow
|
package = Workflow
|
||||||
core = 7.x
|
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"
|
core = "7.x"
|
||||||
project = "workflow"
|
project = "workflow"
|
||||||
datestamp = "1372980654"
|
datestamp = "1484510888"
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
Executable → Regular
+268
-151
@@ -1,155 +1,292 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* @file
|
* @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,
|
* Why it's own module? Some sites prefer rules, some prefer actions,
|
||||||
* all prefer a lower code footprint and better performance.
|
* 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.
|
* the initial push to split actions out of core workflow.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_hook_info().
|
* Implements hook_workflow_operations().
|
||||||
* Expose each transition as a hook.
|
*
|
||||||
|
* Menu callback, called in workflow.admin.inc to add actions for states.
|
||||||
*/
|
*/
|
||||||
function workflow_actions_trigger_info() {
|
function workflow_actions_workflow_operations($level, $workflow = NULL, $state = NULL) {
|
||||||
$states = array();
|
if ($workflow) {
|
||||||
foreach (workflow_get_workflow_states() as $data) {
|
|
||||||
$states[$data->sid] = check_plain($data->state);
|
|
||||||
}
|
|
||||||
if (empty($states)) {
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
$trigger_page = drupal_substr($_GET['q'], 0, 32) == 'admin/structure/trigger/workflow';
|
|
||||||
|
|
||||||
// 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));
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
$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(
|
return array(
|
||||||
'workflow' => $pseudohooks,
|
'workflow_overview_actions' => array(
|
||||||
|
'title' => t('Actions'),
|
||||||
|
'href' => 'admin/structure/trigger/workflow/' . $workflow->wid,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
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.'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_workflow().
|
* 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) {
|
function workflow_actions_workflow($op, $id, $new_sid, $entity, $force = FALSE, $entity_type = 'node', $field_name = '', $transition = NULL) {
|
||||||
switch ($op) {
|
switch ($op) {
|
||||||
case 'transition post':
|
case 'transition post':
|
||||||
// A transition has occurred; fire off actions associated with this transition.
|
// Reminder: event 'transition post' does not occur for Workflow Field.
|
||||||
if ($transition = workflow_get_workflow_transitions_by_sid_target_sid($old_state, $new_state) ) {
|
_workflow_actions_do($transition);
|
||||||
$hook = 'workflow-' . $node->type . '-' . $transition->tid;
|
break;
|
||||||
$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;
|
|
||||||
|
|
||||||
case 'transition delete':
|
case 'transition delete':
|
||||||
if ($transition = workflow_get_workflow_transitions_by_sid_target_sid($old_state, $new_state) ) {
|
// @todo: implement delete triggers upon 'transition delete'.
|
||||||
$actions = workflow_actions_get_actions_by_tid($transition->tid);
|
// The below code generates an error PDOException: SQLSTATE[42S22].
|
||||||
foreach ($actions as $aid) {
|
// So, it is commented out as per d.o. #2200089. In 7.x-1.2, this trigger
|
||||||
workflow_actions_remove($transition->tid, $aid);
|
// was never invoked. So, 'delete trigger upon transition delete' is now
|
||||||
}
|
// a feature request.
|
||||||
}
|
// $tid = $id;
|
||||||
break;
|
// $actions = _workflow_actions_get_actions_by_tid($tid);
|
||||||
|
// foreach ($actions as $aid) {
|
||||||
|
// _workflow_actions_remove($tid, $aid);
|
||||||
|
// }
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_workflow_operations().
|
* Implements hook_entity_insert().
|
||||||
* Called in workflow.admin.inc to add actions for states.
|
*
|
||||||
|
* 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_workflow_operations($level, $workflow = NULL, $state = NULL) {
|
function workflow_actions_entity_insert($entity, $type) {
|
||||||
if ($workflow) {
|
workflow_actions_entity_update($entity, $type);
|
||||||
return array('workflow_overview_actions' => array(
|
}
|
||||||
'title' => t('Actions'),
|
|
||||||
'href' => 'admin/structure/trigger/workflow/' . $workflow->wid),
|
/**
|
||||||
);
|
* 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() {
|
||||||
|
static $pseudohooks = array();
|
||||||
|
|
||||||
|
if ($pseudohooks) {
|
||||||
|
return $pseudohooks;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
$workflows = workflow_load_multiple($wid ? array($wid) : FALSE);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 will not be set if no workflows have been assigned
|
||||||
|
// to node types.
|
||||||
|
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.')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements hook_drupal_alter().
|
||||||
|
*/
|
||||||
|
function workflow_actions_action_info_alter(&$info) {
|
||||||
|
$triggers = workflow_actions_trigger_info();
|
||||||
|
if (empty($triggers)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function, that triggers actions.
|
||||||
|
*
|
||||||
|
* @param $transition.
|
||||||
|
* A Workflow Transition object.
|
||||||
|
*/
|
||||||
|
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.
|
* Remove an action assignment programmatically.
|
||||||
*
|
*
|
||||||
* Helpful when deleting a workflow.
|
* Helpful when deleting a workflow.
|
||||||
*
|
*
|
||||||
* @param $tid
|
* @param int $tid
|
||||||
* Transition ID.
|
* Transition ID.
|
||||||
* @param $aid
|
* @param int $aid
|
||||||
* Action ID.
|
* Action ID.
|
||||||
*/
|
*/
|
||||||
function workflow_actions_remove($tid, $aid) {
|
function _workflow_actions_remove($tid, $aid) {
|
||||||
$ops = array();
|
foreach (_workflow_actions_get_trigger_assignments_by_aid($aid) as $data) {
|
||||||
foreach (workflow_actions_get_trigger_assignments_by_aid($aid) as $data) {
|
|
||||||
// Transition ID is the last part, e.g., foo-bar-1.
|
// Transition ID is the last part, e.g., foo-bar-1.
|
||||||
$transition = array_pop(explode('-', $data->hook));
|
$transition = array_pop(explode('-', $data->hook));
|
||||||
if ($tid == $transition) {
|
if ($tid == $transition) {
|
||||||
@@ -157,8 +294,8 @@ function workflow_actions_remove($tid, $aid) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach ($hooks as $hook) {
|
foreach ($hooks as $hook) {
|
||||||
workflow_actions_delete_trigger_assignments_by_aid_op($aid, $hook);
|
_workflow_actions_delete_trigger_assignments_by_aid_op($aid, $hook);
|
||||||
foreach (workflow_actions_get_actions_by_aid($aid) as $action) {
|
foreach (_workflow_actions_get_actions_by_aid($aid) as $action) {
|
||||||
watchdog('workflow', 'Action %action has been unassigned.',
|
watchdog('workflow', 'Action %action has been unassigned.',
|
||||||
array('%action' => $action->description));
|
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"');
|
$results = db_query('SELECT hook FROM {trigger_assignments} WHERE hook = "workflow"');
|
||||||
return $results->fetchAll();
|
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));
|
$results = db_query('SELECT hook FROM {trigger_assignments} WHERE hook = "workflow" AND aid = ":aid"', array(':aid' => $aid));
|
||||||
return $results->fetchAll();
|
return $results->fetchAll();
|
||||||
}
|
}
|
||||||
@@ -188,33 +325,34 @@ function workflow_actions_get_trigger_assignments_by_aid($aid) {
|
|||||||
/**
|
/**
|
||||||
* Delete assignments, by action and operation.
|
* 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();
|
return db_delete('trigger_assignments')->condition('hook', 'workflow')->condition('hook', $op)->condition('aid', $aid)->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a specific action.
|
* 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));
|
$results = db_query('SELECT * FROM {actions} WHERE aid = ":aid"', array(':aid' => $aid));
|
||||||
return $results->fetchAll();
|
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().
|
* 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();
|
$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 ID is the last part, e.g., foo-bar-1.
|
||||||
$transition = array_pop(explode('-', $data->hook));
|
$transition = array_pop(explode('-', $data->hook));
|
||||||
if ($tid == $transition) {
|
if ($tid == $transition) {
|
||||||
// Specialized, TODO seprate this SQL out later
|
// Specialized, TODO separate this SQL out later.
|
||||||
$results = db_query('SELECT aa.aid, a.type FROM {trigger_assignments} aa ' .
|
$results = db_query('SELECT aa.aid, a.type FROM {trigger_assignments} aa
|
||||||
'LEFT JOIN {actions} a ON aa.aid = a.aid ' .
|
LEFT JOIN {actions} a ON aa.aid = a.aid
|
||||||
'WHERE aa.hook = ":hook" ' .
|
WHERE aa.hook = ":hook"
|
||||||
'ORDER BY weight', array(':hook' => $data->hook));
|
ORDER BY weight', array(':hook' => $data->hook));
|
||||||
foreach ($results as $action) {
|
foreach ($results as $action) {
|
||||||
$aids[$action->aid]['type'] = $action->type;
|
$aids[$action->aid]['type'] = $action->type;
|
||||||
}
|
}
|
||||||
@@ -222,24 +360,3 @@ function workflow_actions_get_actions_by_tid($tid) {
|
|||||||
}
|
}
|
||||||
return $aids;
|
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,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,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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable → Regular
+11
-4
@@ -7,16 +7,18 @@
|
|||||||
/**
|
/**
|
||||||
* Implements hook_workflow_operations().
|
* Implements hook_workflow_operations().
|
||||||
*
|
*
|
||||||
* @param $op
|
* @param string $op
|
||||||
* 'top_actions': Allow modules to insert their own front page action links.
|
* 'top_actions': Allow modules to insert their own front page action links.
|
||||||
* 'operations': Allow modules to insert their own workflow operations.
|
* 'operations': Allow modules to insert their own workflow operations.
|
||||||
* 'state': Allow modules to insert state operations.
|
* 'state': Allow modules to insert state operations.
|
||||||
* @param $workflow
|
* @param object $workflow
|
||||||
* The current workflow object.
|
* The current workflow object.
|
||||||
* @param $state
|
* @param object $state
|
||||||
* The current state object.
|
* 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) {
|
switch ($op) {
|
||||||
case 'top_actions':
|
case 'top_actions':
|
||||||
$actions = array();
|
$actions = array();
|
||||||
@@ -32,6 +34,11 @@ function hook_workflow($op, object $workflow, object $state) {
|
|||||||
// Your module may add to these actions.
|
// Your module may add to these actions.
|
||||||
return $actions;
|
return $actions;
|
||||||
|
|
||||||
|
case 'workflow':
|
||||||
|
$actions = array();
|
||||||
|
// Allow modules to insert their own workflow operations.
|
||||||
|
return $actions;
|
||||||
|
|
||||||
case 'state':
|
case 'state':
|
||||||
$ops = array();
|
$ops = array();
|
||||||
// The workflow_admin_ui module does not use this.
|
// The workflow_admin_ui module does not use this.
|
||||||
|
|||||||
Executable → Regular
+4
-4
@@ -3,17 +3,17 @@
|
|||||||
* Style Sheets for the Workflow_Admin_UI module.
|
* 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. */
|
margin-right: 2em; /* Make room for dragging handle. */
|
||||||
}
|
}
|
||||||
|
|
||||||
#workflow_admin_ui_overview .state-status {
|
#workflow_admin_ui_states .state-status {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#workflow_admin_ui_overview .state-count {
|
#workflow_admin_ui_states .state-count {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
#workflow_admin_ui_overview th.state-ops {
|
#workflow_admin_ui_states th.state-ops {
|
||||||
}
|
}
|
||||||
|
|||||||
Executable → Regular
+7
-4
@@ -1,14 +1,17 @@
|
|||||||
name = Workflow UI
|
name = Workflow UI
|
||||||
description = "Provides administrative UI for workflow."
|
description = "Provides administrative UI for workflow."
|
||||||
package = Workflow
|
package = Workflow
|
||||||
dependencies[] = workflow
|
|
||||||
core = 7.x
|
core = 7.x
|
||||||
|
|
||||||
configure = admin/config/workflow/workflow
|
configure = admin/config/workflow/workflow
|
||||||
|
|
||||||
|
dependencies[] = workflow
|
||||||
|
files[] = includes/Entity/EntityWorkflowUIController.php
|
||||||
stylesheets[all][] = workflow_admin_ui.css
|
stylesheets[all][] = workflow_admin_ui.css
|
||||||
|
|
||||||
; Information added by drupal.org packaging script on 2013-07-04
|
; Information added by Drupal.org packaging script on 2017-01-15
|
||||||
version = "7.x-1.2"
|
version = "7.x-2.9+10-dev"
|
||||||
core = "7.x"
|
core = "7.x"
|
||||||
project = "workflow"
|
project = "workflow"
|
||||||
datestamp = "1372980654"
|
datestamp = "1484510888"
|
||||||
|
|
||||||
|
|||||||
Executable → Regular
+11
-17
@@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file
|
* @file
|
||||||
* Install, update and uninstall functions for the workflow_admin_ui module.
|
* 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.
|
* Helper function. Used both by update and enable.
|
||||||
*/
|
*/
|
||||||
function _workflow_admin_ui_participate() {
|
function _workflow_admin_ui_participate() {
|
||||||
|
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'))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
$perms = array('participate in workflow' => 1);
|
||||||
foreach (user_roles() as $rid => $name) {
|
foreach (user_roles() as $rid => $name) {
|
||||||
user_role_change_permissions($rid, $perms);
|
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'))));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
function workflow_admin_ui_update_7001(&$sandbox) {
|
|
||||||
return _workflow_admin_ui_participate();
|
return _workflow_admin_ui_participate();
|
||||||
}
|
}
|
||||||
|
|||||||
Executable → Regular
+112
-1307
File diff suppressed because it is too large
Load Diff
+100
@@ -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¤cy_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,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,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,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') . ' ' . 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,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,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;
|
||||||
|
}
|
||||||
Executable → Regular
+5
-4
@@ -1,12 +1,13 @@
|
|||||||
name = Workflow Clean Up
|
name = Workflow Clean Up
|
||||||
description = "Cleans up Workflow cruft"
|
description = "Cleans up Workflow cruft."
|
||||||
dependencies[] = workflow
|
dependencies[] = workflow
|
||||||
core = 7.x
|
core = 7.x
|
||||||
package = Workflow
|
package = Workflow
|
||||||
|
|
||||||
; Information added by drupal.org packaging script on 2013-07-04
|
configure = admin/config/workflow/workflow/cleanup
|
||||||
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"
|
core = "7.x"
|
||||||
project = "workflow"
|
project = "workflow"
|
||||||
datestamp = "1372980654"
|
datestamp = "1484510888"
|
||||||
|
|
||||||
|
|||||||
Executable → Regular
+20
-183
@@ -11,200 +11,37 @@ function workflow_cleanup_menu() {
|
|||||||
$items = array();
|
$items = array();
|
||||||
|
|
||||||
$items['admin/config/workflow/workflow/cleanup'] = 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'),
|
'access arguments' => array('administer workflow'),
|
||||||
'page callback' => 'drupal_get_form',
|
'page callback' => 'drupal_get_form',
|
||||||
'page arguments' => array('workflow_cleanup_form'),
|
'page arguments' => array('workflow_cleanup_form'),
|
||||||
'type' => MENU_CALLBACK,
|
'type' => MENU_LOCAL_ACTION,
|
||||||
);
|
);
|
||||||
|
|
||||||
return $items;
|
return $items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements hook_help().
|
||||||
|
*/
|
||||||
|
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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_theme().
|
* Implements hook_theme().
|
||||||
*/
|
*/
|
||||||
function workflow_cleanup_theme() {
|
function workflow_cleanup_theme() {
|
||||||
return array(
|
return array(
|
||||||
'workflow_cleanup_form' => array('render element' => 'form'),
|
'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.
|
|
||||||
*/
|
|
||||||
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, '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.
|
|
||||||
*/
|
|
||||||
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)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.)
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
|
||||||
@@ -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'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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\'.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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,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,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,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;
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Install, update and uninstall functions for the Workflow node 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.'));
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
|
||||||
@@ -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,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,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,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";
|
||||||
|
}
|
||||||
Executable → Regular
+4
-4
@@ -1,12 +1,12 @@
|
|||||||
name = Workflow Revert
|
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
|
dependencies[] = workflow
|
||||||
core = 7.x
|
core = 7.x
|
||||||
package = Workflow
|
package = Workflow
|
||||||
|
|
||||||
; Information added by drupal.org packaging script on 2013-07-04
|
; Information added by Drupal.org packaging script on 2017-01-15
|
||||||
version = "7.x-1.2"
|
version = "7.x-2.9+10-dev"
|
||||||
core = "7.x"
|
core = "7.x"
|
||||||
project = "workflow"
|
project = "workflow"
|
||||||
datestamp = "1372980654"
|
datestamp = "1484510888"
|
||||||
|
|
||||||
|
|||||||
Executable → Regular
+66
-59
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file
|
* @file
|
||||||
* Adds an 'Revert' link to the first workflow history row.
|
* Adds an 'Revert' link to the first workflow history row.
|
||||||
@@ -12,8 +13,8 @@ function workflow_revert_permission() {
|
|||||||
'revert workflow' => array(
|
'revert workflow' => array(
|
||||||
'title' => t('Revert workflow'),
|
'title' => t('Revert workflow'),
|
||||||
'description' => t('Allow user to revert workflow state.'),
|
'description' => t('Allow user to revert workflow state.'),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,83 +25,89 @@ function workflow_revert_menu() {
|
|||||||
|
|
||||||
$items['workflow_revert'] = array(
|
$items['workflow_revert'] = array(
|
||||||
'title' => 'Workflow Undo',
|
'title' => 'Workflow Undo',
|
||||||
|
'file' => 'workflow_revert.pages.inc',
|
||||||
'access arguments' => array('revert workflow'),
|
'access arguments' => array('revert workflow'),
|
||||||
'page callback' => 'drupal_get_form',
|
'page callback' => 'drupal_get_form',
|
||||||
'page arguments' => array('workflow_revert_form'),
|
'page arguments' => array('workflow_revert_form'),
|
||||||
'type' => MENU_CALLBACK,
|
'type' => MENU_CALLBACK,
|
||||||
);
|
);
|
||||||
|
|
||||||
return $items;
|
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) {
|
function workflow_revert_rules_event_info() {
|
||||||
$args = array('#nid' => $nid, '#sid' => $sid);
|
$events = array(
|
||||||
|
'workflow_state_reverted' => array(
|
||||||
if (drupal_valid_token($_GET['token'], 'workflow_revert ' . $sid)) {
|
'group' => t('Workflow'),
|
||||||
$states = workflow_get_workflow_states_all();
|
'label' => t('Workflow state reverted'),
|
||||||
$node = node_load($nid);
|
'variables' => rules_events_node_variables(t('updated content'), TRUE),
|
||||||
$args['#node'] = $node;
|
),
|
||||||
$question = t('Are you sure you want to revert %title to the "@state" state?', array(
|
);
|
||||||
'@state' => $states[$sid],
|
return $events;
|
||||||
'%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');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_workflow_history_alter().
|
* Implements hook_workflow_history_alter().
|
||||||
* Add an 'undo' operation for the most recent history change.
|
|
||||||
*
|
*
|
||||||
* @param $variables
|
* Adds an 'undo' operation for the most recent history change.
|
||||||
|
*
|
||||||
|
* @param array $variables
|
||||||
* The current workflow history information as an array.
|
* The current workflow history information as an array.
|
||||||
* 'old_sid' - The state ID of the previous state.
|
* 'transition' - a WorkflowTransition object.
|
||||||
* '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, place it in the 'extra' value.
|
|
||||||
*/
|
*/
|
||||||
function workflow_revert_workflow_history_alter(&$variables) {
|
function workflow_revert_workflow_history_alter(array &$variables) {
|
||||||
|
global $user;
|
||||||
static $first = TRUE;
|
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.
|
// Only mark the first row.
|
||||||
if ($first) {
|
if ($first) {
|
||||||
// Let's ask other modules if the reversion is allowed.
|
$old_sid = $transition->old_sid;
|
||||||
$node = node_load($variables['history']->nid);
|
$new_sid = $transition->new_sid;
|
||||||
$result = module_invoke_all('workflow', 'transition permitted', $variables['sid'], $variables['old_sid'], $node);
|
|
||||||
// Did anybody veto this choice?
|
// Some states are not fit to revert to. In each of these cases, prohibit
|
||||||
if (!in_array(FALSE, $result)) {
|
// to revert to an even older state.
|
||||||
// If not vetoed, mark it.
|
$old_state = $transition->getOldState();
|
||||||
$options = array('query' => array('token' => drupal_get_token('workflow_revert ' . $variables['old_sid'])));
|
if (!$old_state || !$old_state->isActive() || $old_state->isCreationState()) {
|
||||||
$path = 'workflow_revert/' . $variables['history']->nid . '/' . $variables['old_sid'];
|
$first = FALSE;
|
||||||
$variables['extra'] = l('Revert state change', $path, $options);
|
}
|
||||||
|
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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');
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
Executable → Regular
+11
-7
@@ -1,12 +1,16 @@
|
|||||||
name = Workflow rules
|
name = Workflow Rules
|
||||||
description = Provides rules integration for workflows.
|
description = Provides additional, workflow-specific Rules integration.
|
||||||
|
core = 7.x
|
||||||
|
package = Workflow
|
||||||
|
|
||||||
dependencies[] = workflow
|
dependencies[] = workflow
|
||||||
dependencies[] = rules
|
dependencies[] = rules
|
||||||
package = Workflow
|
|
||||||
core = 7.x
|
configure = admin/config/workflow/rules
|
||||||
; 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"
|
core = "7.x"
|
||||||
project = "workflow"
|
project = "workflow"
|
||||||
datestamp = "1372980654"
|
datestamp = "1484510888"
|
||||||
|
|
||||||
|
|||||||
Executable → Regular
+8
-27
@@ -1,35 +1,16 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* @file
|
* @file
|
||||||
* Provide rules for workflows.
|
* Provides Rules integration for Workflow.
|
||||||
|
*
|
||||||
* Why it's own module? Some sites prefer rules, some prefer actions,
|
* Why it's own module? Some sites prefer rules, some prefer actions,
|
||||||
* all prefer a lower code footprint and better performance.
|
* 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.
|
* the initial push to split rules out of core workflow.
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements hook_workflow().
|
|
||||||
*
|
*
|
||||||
* @param $op
|
* This file is intentionally left blanc.
|
||||||
* The current workflow operation: 'transition pre' or 'transition post'.
|
* The code is in the workflow_rules.workflow.inc file.
|
||||||
* @param $old_state
|
* Drupal 7 requires a .module file, even if it is empty.
|
||||||
* 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_rules_workflow($op, $old_state, $new_state, $node) {
|
|
||||||
switch ($op) {
|
require_once dirname(__FILE__) . '/workflow_rules.rules-callback.inc';
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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,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);
|
||||||
|
}
|
||||||
Executable → Regular
+30
-185
@@ -1,25 +1,31 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* @file
|
* @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().
|
* Implements hook_rules_event_info().
|
||||||
|
*
|
||||||
|
* @todo: add support for any entity type in hook_rules_event_info.
|
||||||
*/
|
*/
|
||||||
function workflow_rules_rules_event_info() {
|
function workflow_rules_rules_event_info() {
|
||||||
$events = array(
|
$events = array();
|
||||||
'workflow_state_changed' => array(
|
if (module_exists('workflownode')) {
|
||||||
'group' => t('Workflow'),
|
$events += _workflownode_rules_event_info();
|
||||||
'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),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return $events;
|
return $events;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,184 +33,23 @@ function workflow_rules_rules_event_info() {
|
|||||||
* Implements hook_rules_condition_info().
|
* Implements hook_rules_condition_info().
|
||||||
*/
|
*/
|
||||||
function workflow_rules_rules_condition_info() {
|
function workflow_rules_rules_condition_info() {
|
||||||
return array(
|
$conditions = array();
|
||||||
'workflow_check_transition' => array(
|
if (module_exists('workflownode')) {
|
||||||
'group' => t('Workflow'),
|
$conditions += _workflownode_rules_condition_info();
|
||||||
'label' => t('Check workflow transition'),
|
}
|
||||||
'parameter' => array(
|
return $conditions;
|
||||||
'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.'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_rules_action_info().
|
* Implements hook_rules_action_info().
|
||||||
*/
|
*/
|
||||||
function workflow_rules_rules_action_info() {
|
function workflow_rules_rules_action_info() {
|
||||||
return array(
|
$actions = array();
|
||||||
'workflow_rules_set_state' => array(
|
if (module_exists('workflownode')) {
|
||||||
'group' => t('Workflow'),
|
$actions += _workflownode_rules_action_info();
|
||||||
'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;
|
if (module_exists('workflowfield')) {
|
||||||
return $options;
|
$actions += _workflowfield_rules_action_info();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
return $actions;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
workflow_transition($node, $sid, TRUE);
|
|
||||||
unset($node->workflow_comment);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable → Regular
+6
-5
@@ -1,13 +1,14 @@
|
|||||||
name = Workflow Search API
|
name = Workflow Node Search API
|
||||||
description = "Adds workflow state information to Search API index"
|
description = "Adds Workflow Node's state information to Search API index."
|
||||||
dependencies[] = workflow
|
dependencies[] = workflow
|
||||||
|
dependencies[] = workflownode
|
||||||
dependencies[] = entity
|
dependencies[] = entity
|
||||||
core = 7.x
|
core = 7.x
|
||||||
package = Workflow
|
package = Workflow
|
||||||
|
|
||||||
; Information added by drupal.org packaging script on 2013-07-04
|
; Information added by Drupal.org packaging script on 2017-01-15
|
||||||
version = "7.x-1.2"
|
version = "7.x-2.9+10-dev"
|
||||||
core = "7.x"
|
core = "7.x"
|
||||||
project = "workflow"
|
project = "workflow"
|
||||||
datestamp = "1372980654"
|
datestamp = "1484510888"
|
||||||
|
|
||||||
|
|||||||
Executable → Regular
+9
-13
@@ -2,6 +2,8 @@
|
|||||||
/**
|
/**
|
||||||
* @file
|
* @file
|
||||||
* Adds workflow state information to Search API index.
|
* 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) {
|
function workflow_search_api_entity_property_info_alter(&$info) {
|
||||||
$info['node']['properties']['workflow_state_name'] = array(
|
$info['node']['properties']['workflow_state_name'] = array(
|
||||||
'type' => 'text',
|
'type' => 'text',
|
||||||
'label' => t('Workflow state name'),
|
'label' => t('Workflow state label'),
|
||||||
'sanitized' => TRUE,
|
'sanitized' => TRUE,
|
||||||
'getter callback' => 'workflow_search_api_property_workflow_state_getter_callback',
|
'getter callback' => 'workflow_search_api_property_workflow_state_getter_callback',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Getter callback for workflow state defined in workflow_search_api_entity_property_info_alter.
|
* Getter callback for workflow state defined in workflow_search_api_entity_property_info_alter.
|
||||||
*/
|
*/
|
||||||
function workflow_search_api_property_workflow_state_getter_callback($item) {
|
function workflow_search_api_property_workflow_state_getter_callback($node) {
|
||||||
if ($item->workflow) {
|
// Get text value of workflow state. Only for Workflow Node.
|
||||||
// Get text value of workflow state.
|
$field_name = ''; // An explicit var is needed.
|
||||||
$state = workflow_get_workflow_states_by_sid($item->workflow);
|
$sid = workflow_node_current_state($node, 'node', $field_name);
|
||||||
$state_name = $state->state;
|
return workflow_get_sid_label($sid);
|
||||||
}
|
|
||||||
else {
|
|
||||||
$state_name = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $state_name;
|
|
||||||
}
|
}
|
||||||
Executable → Regular
+2
-2
@@ -22,7 +22,7 @@ the content type.
|
|||||||
|
|
||||||
So: KEEP YOUR CONTENT TYPE NAMES SHORT.
|
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.
|
you have trouble seeing your actions check your name lengths.
|
||||||
|
|
||||||
See further discussion at:
|
See further discussion at:
|
||||||
@@ -33,5 +33,5 @@ See request put to core to make the change at:
|
|||||||
http://drupal.org/node/1062068
|
http://drupal.org/node/1062068
|
||||||
|
|
||||||
Closed and told to have workflow make the table change ourselves. Given that changing
|
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
|
name lengths hazardly would spread the bugs around even more this approach was
|
||||||
not followed.
|
not followed.
|
||||||
@@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
Executable → Regular
+10
-6
@@ -1,12 +1,16 @@
|
|||||||
name = Workflow VBO
|
name = Workflow Actions
|
||||||
description = Provides workflow actions for VBO.
|
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[] = workflow
|
||||||
dependencies[] = views_bulk_operations
|
|
||||||
package = Workflow
|
package = Workflow
|
||||||
core = 7.x
|
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"
|
core = "7.x"
|
||||||
project = "workflow"
|
project = "workflow"
|
||||||
datestamp = "1372980654"
|
datestamp = "1484510888"
|
||||||
|
|
||||||
|
|||||||
Executable → Regular
+15
-131
@@ -1,142 +1,26 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* @file
|
* @file
|
||||||
* Provide workflow actions for VBO.
|
* Provides actions that can be associated to triggers, or used as VBO-action.
|
||||||
* Split out from workflow_actions.
|
*
|
||||||
|
* 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().
|
* Implements hook_action_info().
|
||||||
*/
|
*/
|
||||||
function workflow_vbo_action_info() {
|
function workflow_vbo_action_info() {
|
||||||
return array(
|
$actions = 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(
|
$path = drupal_get_path('module', 'workflow_vbo') . '/actions/';
|
||||||
'type' => 'node',
|
include_once $path . 'given.action.inc';
|
||||||
'label' => t('Change workflow state of post to new state'),
|
include_once $path . 'next.action.inc';
|
||||||
'configurable' => TRUE,
|
|
||||||
'triggers' => array('any'),
|
$actions += workflow_vbo_given_action_info();
|
||||||
),
|
$actions += workflow_vbo_next_action_info();
|
||||||
);
|
|
||||||
}
|
return $actions;
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
$comment = t($context['workflow_comment'], array(
|
|
||||||
'%title' => check_plain($node->title),
|
|
||||||
'%state' => check_plain($context['state_name']),
|
|
||||||
'%user' => theme('username', array('account' => $user)),
|
|
||||||
));
|
|
||||||
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
|
|
||||||
$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'],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+32
@@ -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,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,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,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 have changed in this diff Show More
Reference in New Issue
Block a user