shop, prepare food, eat, and clean up.');
case strstr($section, 'admin/workflow/actions') && (sizeof($section) == 22):
return t('Use this page to set actions to happen when transitions occur. To %conf_actions, use the actions module.', array('%conf_actions' => l('configure actions', 'admin/actions')));
}
}
/**
* Implementation of hook_perm().
*/
function workflow_perm() {
return array('administer workflow');
}
/**
* Implementation of hook_menu().
*/
function workflow_menu($may_cache) {
$items = array();
$access = user_access('administer workflow');
if ($may_cache) {
$items[] = array('path' => 'admin/workflow',
'title' => t('workflow'),
'access' => $access,
'callback' => 'workflow_overview');
$items[] = array('path' => 'admin/workflow/edit',
'title' => t('Edit workflow'),
'access' => $access,
'type' => MENU_CALLBACK,
'callback' => 'workflow_edit');
$items[] = array('path' => 'admin/workflow/list',
'title' => t('List'),
'access' => $access,
'weight' => -10,
'callback' => 'workflow_page',
'type' => MENU_DEFAULT_LOCAL_TASK);
$items[] = array('path' => 'admin/workflow/add',
'title' => t('Add workflow'),
'access' => $access,
'weight' => -8,
'callback' => 'workflow_add_form',
'type' => MENU_LOCAL_TASK);
$items[] = array('path' => 'admin/workflow/state',
'title' => t('Add state'),
'access' => $access,
'type' => MENU_CALLBACK,
'callback' => 'workflow_state_add');
$items[] = array('path' => 'admin/workflow/delete',
'title' => t('Delete workflow'),
'access' => $access,
'type' => MENU_CALLBACK,
'callback' => 'workflow_delete');
$items[] = array('path' => 'admin/workflow/actions',
'title' => t('Workflow actions'),
'access' => $access,
'type' => MENU_CALLBACK,
'callback' => 'workflow_actions_form');
$items[] = array('path' => 'admin/workflow/actions/remove',
'title' => t('Workflow actions'),
'access' => $access,
'type' => MENU_CALLBACK,
'callback' => 'workflow_actions_remove_form');
}
else {
if (arg(0) == 'node' && is_numeric(arg(1))) {
$node = node_load(arg(1));
$wid = workflow_get_workflow_for_type($node->type);
if ($wid) { // workflow exists for this type
global $user;
$roles = array_keys($user->roles);
if ($node->uid == $user->uid) {
$roles = array_merge(array('author'), $roles);
}
$workflow = db_fetch_object(db_query("SELECT * FROM {workflows} WHERE wid = %d", $wid));
$allowed_roles = $workflow->tab_roles ? explode(',', $workflow->tab_roles) : array();
$items[] = array('path' => "node/$node->nid/workflow",
'title' => t('workflow'),
'access' => array_intersect($roles, $allowed_roles) || user_access('administer nodes'),
'type' => MENU_LOCAL_TASK,
'weight' => 2,
'callback' => 'workflow_tab_page',
'callback arguments' => arg(1),
);
}
}
}
return $items;
}
function workflow_tab_page($nid) {
drupal_set_title(t('workflow'));
$node = node_load($nid);
$wid = workflow_get_workflow_for_type($node->type);
$states_per_page = 20;
$states = workflow_get_states($wid) + array(t('(creation)'));
$current = workflow_node_current_state($node);
$output = '
'. t('Current state: ') . check_plain($states[$current]) . "
\n";
$choices = workflow_field_choices($node);
$min = $states[$current] == t('(creation)') ? 1 : 2;
if (count($choices) >= $min) { // bail out if user has no new target state(s)
ksort($choices);
$wid = workflow_get_workflow_for_type($node->type);
$name = check_plain(workflow_get_name($wid));
$form = array();
workflow_node_form($form, t('Change %s state', array('%s' => $name)), $name, $current, $choices);
$form['node'] = array(
'#type' => 'value',
'#value' => $node,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit')
);
$output .= drupal_get_form('workflow_tab', $form);
}
$result = pager_query("SELECT h.*, u.name FROM {workflow_node_history} h LEFT JOIN {users} u ON h.uid = u.uid WHERE nid = %d ORDER BY stamp DESC", $states_per_page, 0, NULL, $nid);
$rows = array();
while ($history = db_fetch_object($result)) {
$rows[] = array(
format_date($history->stamp),
check_plain($states[$history->old_sid]),
check_plain($states[$history->sid]),
theme('username', $history),
check_plain($history->comment)
);
}
$output .= theme('table', array(t('Date'), t('Old State'), t('New State'), t('By'), t('Comment')), $rows, array('class' => 'workflow_history'), t('Workflow History'));
$output .= theme('pager', $states_per_page);
return $output;
}
function workflow_tab_submit($form_id, $form_values) {
$node = $form_values['node'];
$sid = $form_values['workflow'];
// make sure new state is a valid choice
if (array_key_exists($sid, workflow_field_choices($node))) {
workflow_execute_transition($node, $sid, $form_values['workflow_comment']); // do transition
}
return 'node/' . $node->nid;
}
/**
* Implementation of hook_nodeapi().
* Summary of nodeapi ops we can see (Drupal 4.7):
*
* preview submit submit preview
* (from (from (from (from
* add add) add) view edit edit) edit)
* -------- -------- -------- ----- ------- -------- --------
* load load load load
* prepare prepare prepare prepare prepare prepare
* validate validate validate validate
* view view view
* submit submit
* insert update
*
*/
function workflow_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
switch ($op) {
case 'load':
$node->_workflow = workflow_node_current_state($node);
break;
case 'insert':
case 'update':
// stop if no workflow for this node type
$wid = workflow_get_workflow_for_type($node->type);
if (!$wid) {
break;
}
// get new state
$sid = $node->workflow;
if (!$sid && $op == 'insert') { // if not specified, use first valid
$choices = workflow_field_choices($node);
$keys = array_keys($choices);
$sid = array_shift($keys);
}
// make sure new state is a valid choice
if (array_key_exists($sid, workflow_field_choices($node))) {
workflow_execute_transition($node, $sid, $node->workflow_comment); // do transition
}
break;
case 'delete':
db_query("DELETE FROM {workflow_node} WHERE nid = %d", $node->nid);
break;
}
}
/**
* Add the actual form widgets for workflow change to the passed in form.
*
* @param array $form
* @param string $name
* @param string $current
* @param array $choices
*/
function workflow_node_form(&$form, $title, $name, $current, $choices) {
if (sizeof($choices) == 1) {
$form['workflow'][$name] = array(
'#type' => 'hidden',
'#value' => $current
);
}
else {
$form['workflow'][$name] = array(
'#type' => 'radios',
'#title' => $title,
'#options' => $choices,
'#name' => $name,
'#parents' => array('workflow'),
'#default_value' => $current
);
$form['workflow']['workflow_comment'] = array(
'#type' => 'textarea',
'#title' => t('Comment'),
'#description' => t('A comment to put in the workflow log.'),
);
}
}
/**
* Generate a forms API compliant workflow field.
*
* @param object &$node
* @return array
*/
function workflow_form_alter($form_id, &$form) {
if (isset($form['type']) && $form['type']['#value'] .'_node_form' == $form_id) {
$node = $form['#node'];
$choices = workflow_field_choices($node);
$wid = workflow_get_workflow_for_type($node->type);
$states = workflow_get_states($wid) + array(t('(creation)'));
$current = workflow_node_current_state($node);
$min = $states[$current] == t('(creation)') ? 1 : 2;
if (count($choices) < $min) { // bail out if user has no new target state(s)
return;
}
ksort($choices);
$name = check_plain(workflow_get_name($wid));
// if the current node state is not one of the choices, autoselect first choice
// we know all states in $choices are states that user has permission to
// go to because workflow_field_choices() has already checked that
if (!isset($choices[$current])) {
$array = array_keys($choices);
$current = $array[0];
}
if (sizeof($choices) > 1) {
$form['workflow'] = array(
'#type' => 'fieldset',
'#title' => $name,
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
}
workflow_node_form($form, $name, $name, $current, $choices);
}
}
/**
* Execute a transition (change state of a node).
*
* @param object $node
* @param int $sid
* @return int ID of new state.
*/
function workflow_execute_transition($node, $sid, $comment = NULL) {
$old_sid = workflow_node_current_state($node);
if ($old_sid == $sid) { // stop if not going to a different state
// Write comment into history though.
if ($comment) {
_workflow_write_history($node, $sid, $comment);
}
return;
}
// Make sure this transition is valid and allowed for the current user.
global $user;
if ($user->uid > 1) { // allow any state change for superuser (might be cron)
$tid = workflow_get_transition_id($old_sid, $sid);
if (!$tid) {
watchdog('workflow', t('Attempt to go to nonexistent transition (from %old to %new)', array('%old' => $old_sid, '%new' => $sid), WATCHDOG_ERROR));
return;
}
if (!workflow_transition_allowed($tid, array_merge(array_keys($user->roles), array('author')))) {
watchdog('workflow', t('User %user not allowed to go from state %old to %new)', array('%user' => $user->name, '%old' => $old_sid, '%new' => $sid), WATCHDOG_NOTICE));
return;
}
}
// Invoke a callback indicating a transition is about to occur. Modules
// may veto the transition by returning FALSE.
$result = module_invoke_all('workflow', 'transition pre', $old_sid, $sid, $node);
if (in_array(FALSE, $result)) { // stop if a module says so
return;
}
_workflow_node_to_state($node, $sid, $comment); // change the state
// Register state change with watchdog
$state_name = db_result(db_query("SELECT state FROM {workflow_states} WHERE sid = %d", $sid));
$type = check_plain(node_get_name($node->type)); //module_invoke($node->type, 'node_name', $node);
$state_name = check_plain($state_name);
watchdog('workflow', t('State of %type %node_title set to %state_name', array('%type' => $type, '%node_title' => theme('placeholder', $node->title), '%state_name' => $state_name)), WATCHDOG_NOTICE, l('view', $node->nid));
// Notify modules that transition has occurred. Actions should take place
// in response to this callback, not the previous one.
module_invoke_all('workflow', 'transition post', $old_sid, $sid, $node);
}
/**
* Implementation of a Drupal action.
* Changes the workflow state of a node to the next state of the workflow.
*
*/
function action_workflow_execute_transition($op, $edit = array(), &$node) {
switch($op) {
case 'do':
// if this action is being fired because it's attached to a workflow transition
// then the node's new state (now it's current state) should be in $node->workflow;
// otherwise the current state is placed in $node->_workflow by our nodeapi load
if (!isset($node->workflow) && !isset($node->_workflow)) {
watchdog('workflow', t('Unable to get current workflow state of node %nid.', array('%nid' => $node->nid)));
return;
}
$current_state = isset($node->workflow) ? $node->workflow : $node->_workflow;
// get the node's new state
//$new_state = $edit['target_state']; // change to specific state not yet implemented
$new_state = '';
if ($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
watchdog('action', t('Changing workflow state of node id %id to %state', array('%id' => intval($node->nid), '%state' => check_plain($new_state_name))));
workflow_execute_transition($node, $new_state);
watchdog('action', t('Changed workflow state of node id %id to %state', array('%id' => intval($node->nid), '%state' => check_plain($new_state_name))));
break;
case 'metadata':
return array(
'description' => t('Change workflow state of a node to next state'),
'type' => t('Workflow'),
'batchable' => TRUE,
'configurable' => FALSE,
);
// return an HTML config form for the action
case 'form':
return '';
// validate the HTML form
case 'validate':
return TRUE;
// process the HTML form to store configuration
case 'submit':
return '';
}
}
/**
* Get the states one can move to for a given node.
*
* @param object $node
* @return array
*/
function workflow_field_choices($node) {
global $user;
$wid = workflow_get_workflow_for_type($node->type);
if (!$wid) { // no workflow for this type
return array();
}
$states = workflow_get_states($wid);
$roles = array_keys($user->roles);
$current_sid = workflow_node_current_state($node);
if ($user->uid == $node->uid && $node->uid > 0) { // if the node author
$roles += array('author' => 'author');
}
if ($user->uid == 1) { // if the superuser
$roles = 'ALL';
}
$transitions = workflow_allowable_transitions($current_sid, 'to', $roles);
if ($current_sid != _workflow_creation_state($wid)) { // include current state if not (creation)
$transitions = array($current_sid => $states[$current_sid]) + $transitions;
}
return $transitions;
}
/**
* Get the current state of a given node.
*
* @param object $node
* @return string state ID
*/
function workflow_node_current_state($node) {
$sid = db_result(db_query("SELECT sid FROM {workflow_node} WHERE nid=%d ", $node->nid));
if (!$sid) {
$wid = workflow_get_workflow_for_type($node->type);
$sid = _workflow_creation_state($wid);
}
return $sid;
}
function _workflow_creation_state($wid) {
return db_result(db_query("SELECT sid FROM {workflow_states} WHERE ".
"wid=%d AND sysid=%d", $wid, WORKFLOW_CREATION));
}
/**
* Implementation of hook_workflow().
*/
function workflow_workflow($op, $old_state, $new_state, $node) {
switch ($op) {
case 'transition pre':
break;
case 'transition post':
// a transition has occurred; fire off actions associated with this transition
// an SQL guru could clean this up with a complicated JOIN
$tid = workflow_get_transition_id($old_state, $new_state);
if ($tid) {
$actions_this_tid = workflow_get_actions($tid);
if ($actions_this_tid && function_exists('actions_do')) {
actions_do(array_keys($actions_this_tid), $node);
}
}
break;
}
}
/**
* Create the form for adding/editing a workflow.
*
* @param $name
* Name of the workflow if editing.
* @param $add
* Boolean, if true edit workflow name.
*
* @return
* HTML form.
*
*/
function workflow_add_form($name = NULL) {
$form = array();
$form['wf_name'] = array(
'#type' => 'textfield',
'#title' => t('Workflow Name'),
'#size' => '16',
'#maxlength' => '254',
'#default_value' => $name
);
$form['submit'] = array('#type' => 'submit', '#value' => t('Add Workflow'));
$output = drupal_get_form('workflow_add_form', $form);
return $output;
}
function workflow_add_form_validate($form_id, $form_values) {
if ($form_values['wf_name'] == '') {
form_set_error('', t('Please provide a nonblank name for the new workflow.'));
}
}
function workflow_add_form_submit($form_id, $form_values) {
if (array_key_exists('wf_name', $form_values) && $form_values['wf_name'] != '') {
$wf_name = $form_values['wf_name'];
workflow_create($wf_name);
drupal_set_message(t('The workflow %wf was created. You should now add states to your workflow.', array('%wf' => theme('placeholder', $wf_name))));
drupal_goto('admin/workflow');
}
}
/**
* Create the form for confirmation of deleting a workflow.
*
* @param $wid
* The ID of the workflow.
*
* @return
* HTML form.
*
*/
function workflow_delete($wid, $sid = NULL) {
if (isset($sid)) {
return workflow_state_delete_form($wid, $sid);
}
$wf = workflow_get_name($wid);
$form = array();
$form['wid'] = array('#type' => 'value', '#value' => $wid);
$output = confirm_form('workflow_delete_confirm', $form, t('Are you sure you want to delete %title? All nodes that have a workflow state associated with this workflow will have those workflow states removed.', array('%title' => theme('placeholder', $wf))), $_GET['destination'] ? $_GET['destination'] : 'admin/workflow', t('This action cannot be undone.'), t('Delete'), t('Cancel') );
return $output;
}
function workflow_delete_confirm_submit($form_id, $form_values) {
if ($form_values['confirm'] == 1) {
$wid = $form_values['wid'];
$wf = workflow_get_name($wid);
workflow_deletewf($wid);
drupal_set_message(t('The workflow %wf was deleted.', array('%wf' => theme('placeholder', $wf))));
drupal_goto('admin/workflow');
}
}
/**
* View workflow permissions by role
*
* @param $wid
* The ID of the workflow
*/
function workflow_permissions($wid) {
$name = workflow_get_name($wid);
$result = db_query("SELECT sid, state FROM {workflow_states} WHERE wid = %d", $wid);
$states = array();
while ($data = db_fetch_object($result)) {
$states[$data->sid] = $data->state;
}
$all = array();
$result = db_query("SELECT * FROM {workflow_transitions}");
while ($data = db_fetch_object($result)) {
if (isset($states[$data->sid])) {
foreach (explode(',', $data->roles) as $role) {
$all[$role][] = array($data->sid, $data->target_sid);
}
}
}
$output = '';
$role_names = user_roles();
$header = array(t('From'), '', t('To'));
foreach ($all as $role_id => $transitions) {
$rows = array();
$role = $role_id == 'author' ? 'author' : $role_names[$role_id];
foreach ($transitions as $transition) {
list($from, $to) = $transition;
$rows[] = array($states[$from], WORKFLOW_ARROW, $states[$to]);
}
$output .= '' . t("%role may do these transitions:", array('%role' => $role)) . '
';
if ($rows) {
$output .= theme('table', $header, $rows);
}
else {
$output .= t('None');
}
}
return $output;
}
/**
* Menu callback to edit a workflow's properties.
*
* @param $wid
* The ID of the workflow.
*
* @return
* HTML form.
*
*/
function workflow_edit($wid) {
// $name = workflow_get_name($wid);
$workflow = db_fetch_object(db_query("SELECT * FROM {workflows} WHERE wid = %d", $wid));
$form['wf_name'] = array(
'#type' => 'textfield',
'#default_value' => $workflow->name,
'#title' => t('Workflow Name'),
'#size' => '16',
'#maxlength' => '254',
);
$form['tab'] = array(
'#type' => 'fieldset',
'#title' => t('Workflow tab permissions'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$form['tab']['tab_roles'] = array(
'#type' => 'checkboxes',
'#options' => workflow_get_roles(),
'#default_value' => explode(',', $workflow->tab_roles),
'#description' => t('Select any roles that should have access to the workflow tab on nodes that have a workflow.'),
);
$form['transitions'] = workflow_transition_grid_form($wid);
$form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
$form['wid'] = array('#type' => 'value', '#value' => $wid);
$form['#theme'] = 'workflow_edit';
$form['transitions']['#tree'] = TRUE;
$output = drupal_get_form('workflow_edit', $form);
$output .= workflow_permissions($wid);
return $output;
}
function theme_workflow_edit($form) {
$output = form_render($form['wf_name']);
$wid = $form['wid']['#value'];
$states = workflow_get_states($wid);
if ($states) {
$roles = workflow_get_roles();
$header = array(array('data' => t('From / To ') . ' ' . WORKFLOW_ARROW));
$rows = array();
foreach ($states as $state_id => $name) {
if ($name != t('(creation)')) { // don't allow transition TO (creation)
$header[] = array('data' => t($name));
}
$row = array(array('data' => $name));
foreach ($states as $nested_state_id => $nested_name) {
if ($nested_name == t('(creation)')) {
continue; // don't allow transition TO (creation)
}
if ($nested_state_id != $state_id) {
// need to render checkboxes for transition from $state to $nested_state
$from = $state_id;
$to = $nested_state_id;
$cell = '';
foreach ($roles as $rid => $role_name) {
$cell .= form_render($form['transitions'][$from][$to][$rid]);
//$cell .= form_render($form['transitions']["transitions][$from][$to][$rid"]);
}
$row[] = array('data' => $cell);
}
else {
$row[] = array('data' => '');
}
}
$rows[] = $row;
}
$output .= theme('table', $header, $rows);
} else {
$output = 'There are no states defined for this workflow.';
}
$output .= form_render($form);
return $output;
}
function workflow_edit_validate($form_id, $form_values) {
$wid = $form_values['wid'];
if (array_key_exists('wf_name', $form_values) && $form_values['wf_name'] != '') {
// validate: make sure workflow name is not a duplicate
$dupe_name = db_result(db_query("SELECT COUNT(*) FROM {workflows} WHERE name='%s' AND wid!=%d", $form_values['wf_name'], $wid));
if ($dupe_name) {
form_set_error('', t('Warning: Another workflow with this name already exists.'));
}
// validate: make sure 'author' is checked for (creation) -> [something]
$states = workflow_get_states($wid);
$author_creates = 0;
foreach ($form_values['transitions'] as $id => $trans) {
if ($states[$id] == t('(creation)')) {
foreach ($trans as $t) {
if ($t['author'] == 1) { $author_creates++; }
}
}
}
if ($author_creates == 0) {
drupal_set_message(t('Warning: Please give the author permission to go from (creation) to at least one state.'), 'warning');
}
}
else {
form_set_error('', t('Please provide a name for the workflow.'));
}
}
function workflow_edit_submit($form_id, $form_values) {
workflow_update($form_values['wid'], $form_values['wf_name'], array_filter($form_values['tab_roles']));
workflow_update_transitions($form_values['transitions']);
drupal_set_message(t('Workflow updated.'));
return('admin/workflow');
}
/**
* Menu callback to create form to add a workflow state.
*
* @param $wid
* The ID of the workflow.
*
* @return
* HTML form.
*
*/
function workflow_state_add ($wid) {
$form['state_name'] = array(
'#type' => 'textfield',
'#title' => t('State Name'),
'#size' => '16',
'#maxlength' => '254',
);
$form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
$form['wid'] = array('#type' => 'value', '#value' => $wid);
$output = drupal_get_form('workflow_state_add', $form);
return $output;
}
function workflow_state_add_validate($form_id, $form_values) {
if ($form_values['state_name'] == '') {
form_set_error('', t('Please provide a nonblank name for the new state.'));
}
}
function workflow_state_add_submit($form_id, $form_values) {
workflow_state_create($form_values['wid'], $form_values['state_name']);
drupal_set_message(t('The workflow state was created.'));
return('admin/workflow');
}
/**
* Update the transitions for a workflow.
*
* @param array $transitions
* Transitions, for example:
* 18 => array(
* 20 => array(
* 'author' => 1,
* 1 => 0,
* 2 => 1,
* )
* )
* means the transition from state 18 to state 20 can be executed by
* the node author or a user in role 2. The $transitions array should
* contain ALL transitions for the workflow.
*/
function workflow_update_transitions($transitions = array()) {
foreach ($transitions as $from => $to_data) {
foreach ($to_data as $to => $role_data) {
foreach ($role_data as $role => $can_do) {
if ($can_do) {
workflow_transition_add_role($from, $to, $role);
}
else {
workflow_transition_delete_role($from, $to, $role);
}
}
}
}
db_query("DELETE FROM {workflow_transitions} WHERE roles=''");
}
/**
* Add a role to the list of those allowed for a given transition.
* Add the transition if necessary.
* @param int $from
* @param int $to
* @param mixed $role
* Int (role ID) or string ('author').
*/
function workflow_transition_add_role($from, $to, $role) {
$tid = workflow_get_transition_id($from, $to);
if ($tid) {
$roles = db_result(db_query("SELECT roles FROM {workflow_transitions} WHERE tid=%d", $tid));
$roles = explode(',', $roles);
if (array_search($role, $roles) === FALSE) {
$roles[] = $role;
db_query("UPDATE {workflow_transitions} SET roles='%s' WHERE tid=%d", implode(',', $roles), $tid);
}
}
else {
db_query("INSERT INTO {workflow_transitions} (tid, sid, target_sid, roles) VALUES (%d, %d, %d, '%s')", db_next_id('{workflow_transitions}_tid'), $from, $to, $role);
}
}
/**
* Remove a role from the list of those allowed for a given transition.
* @param int $tid
* @param mixed $role
* Int (role ID) or string ('author').
*/
function workflow_transition_delete_role($from, $to, $role) {
$tid = workflow_get_transition_id($from, $to);
if ($tid) {
$roles = db_result(db_query("SELECT roles FROM {workflow_transitions} WHERE tid=%d", $tid));
$roles = explode(',', $roles);
if (($i = array_search($role, $roles)) !== FALSE) {
unset($roles[$i]);
db_query("UPDATE {workflow_transitions} SET roles='%s' WHERE tid=%d", implode(',', $roles), $tid);
}
}
}
/**
* Build the grid of transitions for defining a workflow.
*
* @param int $wid
*/
function workflow_transition_grid_form($wid) {
$roles = workflow_get_roles();
$form = array();
$states = workflow_get_states($wid);
if (!$states) {
$form = array('#type' => 'markup', '#value' => t('There are no states defined for this workflow.'));
return $form;
}
foreach ($states as $state_id => $name) {
foreach ($states as $nested_state_id => $nested_name) {
if ($nested_name == t('(creation)')) {
continue; // don't allow transition TO (creation)
}
if ($nested_state_id != $state_id) {
// need to generate checkboxes for transition from $state to $nested_state
$from = $state_id;
$to = $nested_state_id;
foreach ($roles as $rid => $role_name) {
$tid = workflow_get_transition_id($from, $to);
$form[$from][$to][$rid] = array(
'#type' => 'checkbox',
'#title' => $role_name,
'#default_value' => $tid ? workflow_transition_allowed($tid, $rid) : FALSE);
}
}
}
}
return $form;
}
/**
* See if a transition is allowed for a given role.
*
* @param int $tid
* @param mixed $role
* A single role (int or string 'author') or array of roles.
* @return bool
*/
function workflow_transition_allowed($tid, $role = null) {
$allowed = db_result(db_query(
"SELECT roles FROM {workflow_transitions} WHERE tid=%d", $tid));
$allowed = explode(',', $allowed);
if ($role) {
if (!is_array($role)) { $role = array($role); }
return array_intersect($role, $allowed) == TRUE;
} else {
return $allowed == TRUE; // allowed for anybody?
}
}
/**
* Create the main workflow page, which gives an overview
* of workflows and workflow states.
*
* @return
* HTML form.
*
*/
function workflow_overview() {
$result = db_query("SELECT * FROM {workflows}");
$header = array(array('data' => t('Name')), array('data' => 'Operations', 'colspan' => '4'));
$row = array();
while ($data = db_fetch_object($result)) {
$states = workflow_get_states($data->wid);
$row[] = array(
array('data' => $data->name),
array('data' => l(t('add state'), "admin/workflow/state/$data->wid")),
array('data' => $states && module_exist('actions') ? l(t('actions'), "admin/workflow/actions/$data->wid") : ''),
array('data' => l(t('edit'), "admin/workflow/edit/$data->wid")),
array('data' => l(t('delete'), "admin/workflow/delete/$data->wid")));
if ($states) {
$cell = '';
foreach ($states as $sid => $state) {
if (!workflow_is_system_state($state)) {
$cell .= "" . $state;
$cell .= " | ";
$cell .= l(t('delete'), "admin/workflow/delete/$data->wid/$sid") . " |
";
}
}
$row[] = array(array('data' => $cell, 'colspan' => '5'));
}
}
if (!$row) {
$output = '' . t('No workflows have been added. Would you like to %add_a_workflow?', array('%add_a_workflow' => l(t('add a workflow'), 'admin/workflow/add'))) . '
';
} else {
$output .= theme('table', $header, $row);
}
$output .= workflow_types_form();
return $output;
}
/**
* Tell caller whether a state is a protected system state, such as the creation state.
*
* @param $state
* The name of the state to test
*
* @return
* boolean
*
*/
function workflow_is_system_state($state) {
static $states;
if (!isset($states)) {
$states = array(t('(creation)') => TRUE);
}
return isset($states[$state]);
}
/**
* Create the form for confirmation of deleting a workflow state.
*
* @param $wid
* integer The ID of the workflow.
* @param $sid
* The ID of the workflow state.
*
* @return
* HTML form.
*
*/
function workflow_state_delete_form($wid, $sid) {
$states = workflow_get_states($wid);
$form = array();
$form['wid'] = array('#type' => 'value', '#value' => $wid);
$form['sid'] = array('#type' => 'value', '#value' => $sid);
$output = confirm_form('workflow_state_delete_confirm', $form, t('Are you sure you want to delete %title (and all its transitions)?', array('%title' => theme('placeholder', $states[$sid]))), $_GET['destination'] ? $_GET['destination'] : 'admin/workflow', t('This action cannot be undone.'), t('Delete'), t('Cancel') );
return $output;
}
function workflow_state_delete_confirm_submit($form_id, $form_values) {
$states = workflow_get_states($form_values['wid']);
$state = $states[$form_values['sid']];
if ($form_values['confirm'] == 1) {
workflow_state_delete($form_values['sid']);
drupal_set_message(t('The workflow state %title was deleted.', array('%title' => theme('placeholder', $state))));
}
return('admin/workflow');
}
function workflow_types_form() {
$workflows = workflow_get_all();
if (count($workflows) == 0) {
return '';
}
$workflows[0] = t('None');
$form = array();
$form['workflow_mapping'] = array();
$form['workflow_mapping']['#theme'] = 'workflow_types_form';
$type_map = array();
$result = db_query("SELECT * FROM {workflow_type_map}");
while ($data = db_fetch_object($result)) {
$type_map[$data->type] = $data->wid;
}
$options = $workflows;
$nodetypes = node_get_types();
foreach ($nodetypes as $type => $name) {
$value = isset($type_map[$type]) ? $type_map[$type] : 0;
$form['workflow_mapping'][$type] = array(
'#type' => 'select',
'#prefix' => $name,
'#options' => $options,
'#default_value' => $value
);
}
$form['workflow_mapping']['submit'] = array('#type' => 'submit', '#value' => t('Save Workflow Mapping'));
$form['nodetypes'] = array('#type' => 'value', '#value' => $nodetypes);
return drupal_get_form('workflow_type_form', $form);
}
function theme_workflow_types_form($form) {
$output = '' . t('Each node type may have a separate workflow:') . '
';
$header = array(
array('data' => t('Node Type')),
array('data' => t('Workflow'))
);
$row = array();
foreach (element_children($form) as $key) {
$name = $form[$key]['#prefix'];
unset($form[$key]['#prefix']);
$row[] = array(
array('data' => $name),
array('data' => form_render($form[$key]))
);
}
$output .= theme('table', $header, $row);
return $output;
}
function workflow_type_form_submit($form_id, $form_values) {
workflow_types_save($form_values);
drupal_set_message(t('The workflow mapping was saved.'));
return('admin/workflow');
}
function workflow_actions_remove_form($wid, $tid, $aid) {
$form = array();
$form['wid'] = array('#type' => 'value', '#value' => $wid);
$form['tid'] = array('#type' => 'value', '#value' => $tid);
$form['aid'] = array('#type' => 'value', '#value' => $aid);
$actions = actions_get_all_actions();
$output = confirm_form('workflow_actions_remove_confirm', $form, t('Are you sure you want to delete the action %title?', array('%title' => theme('placeholder', $actions[$aid]['description']))), $_GET['destination'] ? $_GET['destination'] : 'admin/workflow/actions', t('You can add it again later if you wish.'), t('Delete'), t('Cancel') );
return $output;
}
function workflow_actions_remove_confirm_submit($form_id, $form_values) {
if ($form_values['confirm'] == 1) {
$aid = $form_values['aid'];
$actions = actions_actions_map(actions_get_all_actions());
workflow_actions_remove($form_values['tid'], $aid);
watchdog('workflow', t('Action %action deleted', array('%action' => check_plain($actions[$aid]))));
drupal_set_message(t('Action %action deleted.', array('%action' => theme('placeholder', $actions[$aid]))));
return('/admin/workflow/actions/'. $form_values['wid']);
}
}
function workflow_actions_form($wid) {
if (!module_exist('actions')) {
drupal_set_message(t('Before you can assign actions you must install and enable the actions module.'), 'error');
drupal_goto('admin/workflow');
}
$result = workflow_actions_form_build($wid);
$form = $result[1];
$output = workflow_actions_form_build($wid, $form);
return $output[0];
}
function workflow_actions_submit($form, $form_values) {
$tid = $form_values['tid'];
$aid = actions_key_lookup($form_values['action']);
workflow_actions_save($tid, $aid);
// the form does weird things if we don't do a drupal_goto()
$wid = arg(3);
return("admin/workflow/actions/$wid");
}
function workflow_actions_form_build($wid, $form = NULL) {
if (!isset($form)) {
$form = array();
$render = FALSE;
}
else {
$render =TRUE;
}
$output = '';
$states = array();
$all_states = workflow_get_states($wid);
foreach ($all_states as $key => $val) {
$states[$key] = $val;
}
$actions = actions_actions_map(actions_get_all_actions());
$options = array(t('None'));
foreach ($actions as $aid => $action) {
$options[$aid] = $action['description'];
}
$header = array(
array('data' => t('Transition'), 'colspan' => '3'),
array('data' => t('Actions'))
);
$row = array();
foreach ($states as $sid => $state) {
$allowable_to = workflow_allowable_transitions($sid);
$actions_this_tid = array();
// we'll create a row for each allowable transition
foreach ($allowable_to as $to_sid => $name) {
$inner_row = array();
$tid = workflow_get_transition_id($sid, $to_sid);
// get and list the actions that are already assigned to this transition
$actions_this_tid = workflow_get_actions($tid);
$available_options = $options;
foreach ($actions_this_tid as $aid => $act_name) {
$inner_row[] = array(
array('data' => $act_name),
array('data' => l(t('remove'), "admin/workflow/actions/remove/$wid/$tid/$aid"))
);
unset($available_options[$aid]);
}
// list possible actions that may be assigned
if (count($available_options) > 1) {
if ($render) {
$inner_row[] = array(
array('data' => drupal_get_form('workflow_actions_'. $tid, $form[$tid], 'workflow_actions'), 'colspan' => '2'));
}
else {
$form[$tid] = array();
$form[$tid]['action'] = array(
'#prefix' => '',
'#suffix' => ' | ',
'#type' => 'select',
'#options' => $available_options,
'#default_value' => t('None')
);
$form[$tid]['submit'] = array(
'#prefix' => '',
'#suffix' => ' |
',
'#type' => 'submit',
'#value' => t('Add')
);
$form[$tid]['tid'] = array('#type' => 'value', '#value' => $tid);
}
}
$action_table = theme('table', array(), $inner_row);
$row[] = array(
array('data' => $state),
array('data' => WORKFLOW_ARROW),
array('data' => $name),
array('data' => $action_table)
);
}
}
if (count($row) == 0) {
$output .= t('You must first %link before you can assign actions.', array('%link' => l(t('set up transitions'), 'admin/workflow/edit/' . $wid)));
}
else {
$output .= theme('table', $header, $row);
}
return array($output, $form);
}
/**
* Given the ID of a workflow, return its name.
*
* @param integer $wid
* The ID of the workflow.
*
* @return string
* The name of the workflow.
*
*/
function workflow_get_name($wid) {
$name = db_result(db_query("SELECT name FROM {workflows} WHERE wid = %d", $wid));
return $name;
}
/**
* Get ID of a workflow for a node type.
*
* @return int
* The ID of the workflow or FALSE if none.
*
*/
function workflow_get_workflow_for_type($type) {
$wid = db_result(db_query("SELECT wid FROM {workflow_type_map} WHERE type = '%s'", $type));
return $wid > 0 ? $wid : FALSE;
}
/**
* Get names and IDS of all workflows from the database.
*
* @return
* An array of workflows keyed by ID.
*
*/
function workflow_get_all() {
$workflows = array();
$result = db_query("SELECT * FROM {workflows}");
while ($data = db_fetch_object($result)) {
$workflows[$data->wid] = $data->name;
}
return $workflows;
}
/**
* Create a workflow and its (creation) state.
*
* @param $name
* The name of the workflow.
*
*/
function workflow_create($name) {
$wid = db_next_id('{workflows}_wid');
db_query("INSERT INTO {workflows} (wid, name) VALUES (%d, '%s')", $wid, $name);
db_query("INSERT INTO {workflow_states} (sid, wid, state, sysid) VALUES (%d, %d, '%s', %d)",
db_next_id('{workflow_states}_sid'), $wid, t('(creation)'), WORKFLOW_CREATION);
}
/**
* Save a workflow's name in the database.
*
* @param $name
* The name of the workflow.
*
*/
function workflow_update($wid, $name, $tab_roles) {
db_query("UPDATE {workflows} SET name = '%s', tab_roles = '%s' WHERE wid = %d", $name, implode(',', $tab_roles), $wid);
}
/**
* Delete a workflow from the database. Deletes all states,
* transitions and node type mappings too. Removes workflow state
* information from nodes participating in this workflow.
*
* @param $wid
* The ID of the workflow.
*
*/
function workflow_deletewf($wid) {
$wf = workflow_get_name($wid);
$result = db_query("SELECT sid FROM {workflow_states} WHERE wid = %d", $wid);
while ($data = db_fetch_object($result)) {
// delete the state and any associated transitions and actions
workflow_state_delete($data->sid);
db_query("DELETE FROM {workflow_node} WHERE sid = %d", $data->sid);
}
workflow_types_delete($wid);
db_query("DELETE FROM {workflows} WHERE wid = %d", $wid);
watchdog('workflow', t('Deleted workflow') . " '$wf'.");
}
/**
* Load workflow states for a workflow from the database.
*
* @param $wid
* The ID of the workflow.
*
* @return
* An array of workflow states keyed by state ID.
*/
function workflow_get_states($wid) {
$states = array();
$result = db_query("SELECT sid, state FROM {workflow_states} WHERE wid = %d ORDER BY sid", intval($wid));
while ($data = db_fetch_object($result)) {
$states[$data->sid] = $data->state;
}
return $states;
}
/**
* Add a workflow state the database.
*
* @param $wid
* The ID of the workflow.
* @param $name
* A string representing the workflow state, e.g., 'published'.
*
* @return
* The ID of the workflow state
*/
function workflow_state_create($wid, $name) {
$wid = intval($wid);
$sid = db_next_id('{workflow_states}_sid');
db_query("INSERT INTO {workflow_states} (sid, wid, state, sysid) VALUES (%d, %d, '%s', %d)",
$sid, $wid, $name, 0);
return $sid;
}
/**
* Delete a workflow state from the database, including any
* transitions the state was involved in and any associations
* with actions that were made to that transition.
*
* @param $sid
* The ID of the state to delete.
*/
function workflow_state_delete($sid) {
// find out which transitions this state is involved in
$preexisting = array();
$result = db_query("SELECT sid, target_sid FROM {workflow_transitions} WHERE sid = %d OR target_sid = %d", $sid, $sid);
while ($data = db_fetch_object($result)) {
$preexisting[$data->sid][$data->target_sid] = TRUE;
}
// delete the transitions and associated actions
foreach ($preexisting as $from => $array) {
foreach (array_keys($array) as $target_id) {
$tid = workflow_get_transition_id($from, $target_id);
workflow_transition_delete($tid);
}
}
// delete the state
db_query("DELETE FROM {workflow_states} WHERE sid = %d", intval($sid));
}
/**
* Delete a transition (and any associated actions).
*
* @param $tid
* The ID of the transition.
*/
function workflow_transition_delete($tid) {
$actions = workflow_get_actions($tid);
foreach (array_keys($actions) as $aid) {
workflow_actions_remove($tid, $aid);
}
db_query("DELETE FROM {workflow_transitions} WHERE tid = %d", $tid);
}
/**
* Get allowable transitions for a given workflow 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
* Associative array of states (sid=>name pairs), excluding current state.
*
*/
function workflow_allowable_transitions($sid, $dir = 'to', $roles = 'ALL') {
$transitions = array();
$field = $dir == 'to' ? 'target_sid' : 'sid';
$field_where = $dir != 'to' ? 'target_sid' : 'sid';
$result = db_query("SELECT t.tid, t.%s as state_id, s.state as state_name FROM "
. "{workflow_transitions} t INNER JOIN {workflow_states} s ON s.sid = "
. "t.%s WHERE t.%s = %d", $field, $field, $field_where, $sid);
while ($t = db_fetch_object($result)) {
if ($roles == 'ALL' || workflow_transition_allowed($t->tid, $roles)) {
//$state_id = str_pad($t->state_id, 5, '0', STR_PAD_LEFT);
$state_id = $t->state_id;
$transitions[$state_id] = $t->state_name;
}
}
return $transitions;
}
/**
* Save mapping of workflow to node type. E.g., "the story node type
* is using the Foo workflow."
*
* @param $form_values
*/
function workflow_types_save($form_values) {
$nodetypes = node_get_types();
db_query("DELETE FROM {workflow_type_map}");
foreach ($nodetypes as $type => $name) {
db_query("INSERT INTO {workflow_type_map} (type, wid) VALUES ('%s', %d)", $type, intval($form_values[$type]));
}
}
function workflow_types_delete($wid) {
db_query("DELETE FROM {workflow_type_map} WHERE wid = %d", $wid);
}
/**
* Get the actions associated with a given transition.
* @param int $tid
* @return array
* Actions as aid=>description pairs.
*/
function workflow_get_actions($tid) {
$actions = array();
if (!function_exists('actions_do')) {
return $actions;
}
$result = db_query("SELECT a.aid, a.description FROM {actions} a INNER JOIN {workflow_actions} w ON a.aid = w.aid WHERE w.tid = %d", $tid);
while ($data = db_fetch_object($result)) {
$actions[$data->aid] = $data->description;
}
return $actions;
}
/**
* Get the tid of a transition, if it exists.
*
* @param int $from
* ID (sid) of originating state.
* @param int $to
* ID (sid) of target state.
* @return int
* Tid or FALSE if no such transition exists.
*/
function workflow_get_transition_id($from, $to) {
return db_result(db_query("SELECT tid FROM {workflow_transitions} WHERE sid=%d AND target_sid=%d", $from, $to));
}
function workflow_actions_save($tid, $aid) {
actions_register($aid, 'workflow', $tid);
$data = db_fetch_object(db_query("SELECT tid FROM {workflow_actions} WHERE tid = %d AND aid = '%s'", $tid, $aid));
if ($data) return;
db_query("INSERT INTO {workflow_actions} (tid, aid, weight) VALUES (%d, '%s', %d)", $tid, $aid, 0);
}
function workflow_actions_remove($tid, $aid) {
actions_unregister($aid, 'workflow', $tid);
db_query("DELETE FROM {workflow_actions} WHERE tid = %d AND aid = '%s'", $tid, $aid);
}
/**
* Put a node into a state.
* No permission checking here; only call this from other functions that know
* what they're doing.
*
* @see workflow_execute_transition()
*
* @param object $node
* @param int $sid
*/
function _workflow_node_to_state($node, $sid, $comment = NULL) {
global $user;
if (db_result(db_query("SELECT nid FROM {workflow_node} WHERE nid = %d", $node->nid))) {
db_query("UPDATE {workflow_node} SET sid = %d, uid = %d WHERE nid = %d", $sid, $user->uid, $node->nid);
}
else {
db_query("INSERT INTO {workflow_node} (nid, sid, uid) VALUES (%d, %d, %d)", $node->nid, $sid, $user->uid);
}
_workflow_write_history($node, $sid, $comment);
}
function _workflow_write_history($node, $sid, $comment) {
global $user;
db_query("INSERT INTO {workflow_node_history} (nid, old_sid, sid, uid, comment, stamp) VALUES (%d, %d, %d, %d, '%s', %d)", $node->nid, $node->_workflow, $sid, $user->uid, $comment, time());
}
/**
* Function to get a list of roles. Used kind of often.
*/
function workflow_get_roles() {
static $roles = NULL;
if (!$roles) {
$result = db_query('SELECT * FROM {role} ORDER BY name');
$roles = array('author' => 'author');
while ($data = db_fetch_object($result)) {
$roles[$data->rid] = $data->name;
}
}
return $roles;
}
/**
* Implementation of hook_views_tables()
*/
function workflow_views_tables() {
$table = array(
'name' => 'workflow_node',
'provider' => 'workflow',
'join' => array(
'left' => array(
'table' => 'node',
'field' => 'nid',
),
'right' => array(
'field' => 'nid',
),
),
"filters" => array(
'sid' => array(
'name' => t('Workflow: state'),
'operator' => 'views_handler_operator_andor',
'list' => 'workflow_handler_filter_sid',
'help' => t('Include only nodes in the selected workflow states.'),
),
)
);
$tables[$table['name']] = $table;
$table = array(
'name' => 'workflow_states',
'provider' => 'workflow',
'join' => array(
'left' => array(
'table' => 'workflow_node',
'field' => 'sid',
),
'right' => array(
'field' => 'sid',
),
),
"sorts" => array(
'weight' => array(
'name' => t('Workflow: state'),
'field' => array('weight', 'state'),
'help' => t('Order nodes by workflow state.'),
),
),
"fields" => array(
'state' => array(
'name' => t('Workflow: state'),
'sortable' => TRUE,
'help' => t('Display the workflow state of the node.'),
),
),
);
$tables[$table['name']] = $table;
return $tables;
}
/**
* Implementation of hook_views_arguments()
*/
function workflow_views_arguments() {
$arguments = array(
'workflow_state' => array(
'name' => t('Workflow: state'),
'handler' => 'workflow_handler_arg_sid',
'help' => t('The work flow argument allows users to filter a view by workflow state.'),
),
);
return $arguments;
}
/**
* Handler to provide a list of workflow states for the filter list
*/
function workflow_handler_filter_sid() {
$result = db_query("SELECT sid, state FROM {workflow_states} ORDER BY weight, state");
while ($data = db_fetch_object($result)) {
$states[$data->sid] = $data->state;
}
return $states;
}
/**
* Handler to deal with sid as an argument.
*/
function workflow_handler_arg_sid($op, &$query, $argtype, $arg = '') {
switch($op) {
case 'summary':
$query->add_table('workflow_states', TRUE);
$fieldinfo['field'] = "workflow_states.sid";
$query->add_field('sid', 'workflow_states');
$query->add_field('state', 'workflow_states');
$query->add_where('workflow_node.sid IS NOT NULL');
return $fieldinfo;
break;
case 'filter':
$query->add_table('workflow_states', TRUE);
if (is_numeric($arg)) {
$query->add_where("workflow_states.sid = %d", $arg);
}
else {
$query->add_where("workflow_states.state = '%s'", $arg);
}
break;
case 'link':
return l($query->state, "$arg/$query->sid");
case 'title':
$state = db_fetch_object(db_query("SELECT state FROM {workflow_states} WHERE sid=%d", $query));
return $state->state;
}
}