Typical scenario:
- -
A document/node must be moved from 'in progress/draft' to 'edited draft' such that the copy editing team can check grammar and stuff. The 'editor' chooses the 'edited draft' state and an automated email gets sent out alerting a user with role of 'copy editing' to check the copy.
If the given copy editor is too busy, then they assign it to another copy editor.
- -

What's needed here is a system of assigning a user to be responsible for the document in a given state. I believe it's possible to show an appropriate user list based on the state clicked when changing the workflow state.

Considering I can choose what roles can edit a document in a given state then it must be possible to show an applicable user list based on role.

Bonus: Instead of always notifying the same person based on a state transition, we could now notify just the person assigned.

Note that this issue is not a duplicate of http://drupal.org/node/841554 nor http://drupal.org/node/362105 as these things drifted off in some really weird direction.

Also note that a simple solution would involve using the User References module to make a user term list and some really hacky javascript for validation but I don't want to do that... it doesn't feel right.

Comments

dynamicdan’s picture

Assigned: Unassigned » dynamicdan

Ok.. seems kind of quiet here..

My Progress:

  • I am now displaying a valid list of users for the applicable state options. Format is: [username] ([user_roles_applicable])
  • I have a data array I need to send to JS land to enable hiding/showing user options depending on state
  • I need to save the userID...

Easiest way for me to save the userID would be to hack it into the comment field but I think it should be better than that. I haven't worked much with saving to drupal but I assume there is some easy hook/function. I don't want to alter workflow too much so I will see how easy it is to do the right way. Otherwise I'll do it the quick and dirty way that could be improved later.

Thoughts and advice appreciated.

Existing code below.. BTW, is there a workflow_node_form_alter? If I use the hook workflow_form_alter then I won't have all the vars I want ($form, $form_state, $name, $name, $current, $choices, $timestamp, $comment).



// !DD note : custom function (minor changes from workflow_admin_ui_view_permissions)
function workflow_role_permissions($wid) {

    $workflow = workflow_get_workflows_by_wid($wid);
    $name = $workflow->name;
    $all = array();
    $roles = array('author' => t('author')) + user_roles();
    foreach ($roles as $role => $value) {
      $all[$role]['name'] = $value;
    }
    // TODO return to this, this looks simular to actions stuff (transitions) - should be it's own function.
    $result = db_query(
      'SELECT t.roles, s1.state AS state_name, s2.state AS target_state_name, s1.sid AS sid1, s2.sid AS sid2 ' .
      'FROM {workflow_transitions} t ' .
      'INNER JOIN {workflow_states} s1 ON s1.sid = t.sid ' .
      'INNER JOIN {workflow_states} s2 ON s2.sid = t.target_sid ' .
      'WHERE s1.wid = :wid ' .
      'ORDER BY s1.weight ASC , s1.state ASC , s2.weight ASC , s2.state ASC',
      array(':wid' => $wid));
    foreach ($result as $data) {
      foreach (explode(',', $data->roles) as $role) {
        $all[$role]['transitions'][] = array($data->sid1, $data->sid2);
        //array($data->state_name, $data->target_state_name);
      }
    }

    return $all;
}

function workflow_node_form(&$form, $form_state, $title, $name, $current, $choices, $timestamp = NULL, $comment = NULL) {
  // No sense displaying choices if there is only one choice.
  if (sizeof($choices) == 1) {
    $form['workflow'][$name] = array(
      '#type' => 'hidden',
      '#value' => $current
    );
  }
  else {
    $form['workflow'][$name] = array(
      '#type' => 'radios',
      '#title' => !empty($form['#wf']->options['name_as_title']) ? check_plain($title) : '',
      '#options' => $choices,
      '#name' => $name,
      '#parents' => array('workflow'),
      '#default_value' => $current
    );
    
    /********************* START CUSTOM CODE *******************************/
    
    // !DD note : custom addition of a user list
        
    $users = user_load_multiple(FALSE);
    //dpm($users);
        
    $roleTransitions = workflow_role_permissions($form['#wf']->wid);
    dpm($roleTransitions);
   
    $allowedTransitionsByRole = array();
   
   foreach($roleTransitions as $r) {
      $keep = false;
      $stateChangeForRoll = array();
      // this role has transitions
      if(isset($r['transitions'])) {
         foreach($r['transitions'] as $rt) {
            // check if state -> (any choice) is possible
            
            foreach($choices as $cKey => $cVal) {
               if($cKey == $rt[0]) {
                  $keep = true;
                  // don't add twice
                  if(!in_array($cVal, $stateChangeForRoll)) {
                     array_push($stateChangeForRoll, $cVal);
                  }
               }
            }
         }
      }
      if($keep) {
         $allowedTransitionsByRole[$r['name']] = $stateChangeForRoll;
      }
   }

   $roleNames = array();
   foreach($allowedTransitionsByRole as $rKey => $rVal) {
      array_push($roleNames, $rKey);
   }
   dpm($allowedTransitionsByRole);
   dpm($roleNames);


    $uList = array();
    foreach($users as $u) {
      $roleValidForUser = array();
      foreach($u->roles as $ur) {
         if(in_array($ur, $roleNames)) {
            $roleValidForUser[] = $ur;
         }
      }
      if(count($roleValidForUser) > 0) {
         $uRoles = implode(', ', $roleValidForUser);
         $uList[$u->uid] = $u->name ." ({$uRoles})";
      }
    }

    
    $form['workflow']['workflow_assign_user'] = array(
      '#type' => 'radios',
      '#title' => 'Assign user:',
      '#options' => $uList,
      '#name' => 'workflow_assign_user',
      '#default_value' => false
    );

   /************** REST BELOW IS DEFAULT LOGIC ****************/

// Display scheduling form only if a node is being edited and user has
    // permission. State change cannot be scheduled at node creation because
    // that leaves the node in the (creation) state.
    if (!(arg(0) == 'node' && arg(1) == 'add') && user_access('schedule workflow transitions')) {

   // ........... etc.
dynamicdan’s picture

Title: Add option to auto-assign a user as responsible based on the roles with editing permissions for a given state » Add option to assign a user as responsible based on the transitions allowed for each role
Encarte’s picture

IMHO, you may be trying to get from Australia to New Zeeland via London, which may work, but demands a lot of effort and may not develop transportation systems around Oceania.

If I got it right, what you need is a list of the users that, on a given workflow state, have a workflow permission (access permission or transition permission). You also need some users to select other from that list and to trigger an email when that selection happens.

You can create a CCK field on your node that automatically makes such a list and that lets users chose from it. To get only the desired users on the list get http://drupal.org/project/entityreference and http://drupal.org/project/entityreference_view_widget . With them you can make the list with a view, using the exposed filters (formerly known as arguments). The exposed filters let you take information from a context (e. g. the node that is being displayed and its workflow state) and use it to make a list. I don't think there is an exposed filter for pulling out users with some workflow permission, but creating one would probably be a piece of cake compared to what you're trying to do here.

So, this would be a completely no-hack solution, you would learn how to use some essential modules (no, you can't really build serious Drupal sites without the ones I mentioned, let alone building press sites) and you would only have to code (not hack) a new exposed filter for workflow permissions (and, since you like to pay attention to details, you would probably find and crush some bugs on your way).

To trigger an email when a user is selected (and not only when the state changes) you can use the rules module.

Drupal needs new devs like you. Be nice to people and you'll find awesome friends in here. I hope this message was useful.

dynamicdan’s picture

Thanks Encarte.
You raise some good points... I have however just completed my (hacky) implementation.

I don't think there is an exposed filter for pulling out users with some workflow permission, but creating one would probably be a piece of cake compared to what you're trying to do here.

The catch with using a view to show a user list is that one should not reload the page or use AJAX (too slow) to show an applicable list. That suggestion would work, but not in a user friendly way from what I understand.

I've realised now that there are some critical processes that need to be taken into account in assigning users...

  • A default user needs to be assigned (possibly could have been done with rules)
  • The user list should be an added field (helps with filtering via views)
  • User roles must be displayed per user (ideally also full names as apposed to user names)
  • An email needs to be sent only if the user responsible changes (and is not the user creating the content)
  • The applicable state ID needed to be connected to applicable users/roles via CSS so that JS has an easy time showing applicable users
  • The workflow history table needed another field or a token system for the comment field to state the user assigned clearly and easily*
  • The code was not so friendly at points (comment var handling is not consistent)

* if one were to continue to maintain this module with the initial dev mind set, one could use a generic system like tokens to flag content upon transitions. Eg, [uid:109] or [mark:priority] in comments.

Tools I used included: the user reference plugin for showing a list of users, improved workflow summary view to filter by users, a custom module to keep the code separate and hook into transitions and send emails, CSS for hiding certain elements (I felt that putting workflow controls on the edit screen was system overload for the user), some clever JS jQuery.

I could have used rules more but at the time there was a bug of which is now patched I believe. I would've however had a hard time customising emails and their conditions for sending.

In total I have < 250 lines of code + ~70 lines of JS to do everything. If I had more experience with the workflow module and Drupal I could have cut probably >25% of the code.

I can post the code if anyone is interested. If I have time (and if it's possible) then I'd probably use the token system for variable matching instead of regex matching on "Assigned to user: james_smith (Editor, QA Review) (id:201)". Don't know much about enabling tokens though :(

Could tokens used in comments be exposed for views?

dhakshinait’s picture

Can you post your code please?.I need above mentioned requirement for my project and i am working with version 6.Your code will help me to create that same functionality in version 6 too..

dynamicdan’s picture

Seeing as how I usually take more than I give, here's the code:
https://dl.dropbox.com/u/259788/drupal/workflow_addons.zip

Notes:

  • You must add a field to your content types called 'field_user_assigned' (hide it with CSS or field_permissions)
  • I've removed a bunch of private code.. mainly email code. This is obvious in comments and should not affect you
  • I've since added special handling of a contributor type roll. These functions can be removed if not needed. They depend on knowing certain states and node help pages. See defines at the top of the file.
  • I applied some patches to my workflow module as required. Apply if needed or you notice bugs.
  • You'll need to insert 2 'hacky hooks' into the workflow module....

1)

function workflow_execute_transition($node, $sid, $comment = NULL, $force = FALSE) {
  global $user;
  $old_sid = workflow_node_current_state($node);
  
  // !DD note : custom function to set a default comment
  workflow_addons_get_default_comment($node, $sid, $comment);

2)

function workflow_node_form(&$form, $form_state, $title, $name, $current, $choices, $timestamp = NULL, $comment = NULL) {
  // No sense displaying choices if there is only one choice.
  if (sizeof($choices) == 1) {
    $form['workflow'][$name] = array(
      '#type' => 'hidden',
      '#value' => $current
    );
  }
  else {
    $form['workflow'][$name] = array(
      '#type' => 'radios',
      '#title' => !empty($form['#wf']->options['name_as_title']) ? check_plain($title) : '',
      '#options' => $choices,
      '#name' => $name,
      '#parents' => array('workflow'),
      '#default_value' => $current
    );

    // !DD note : calls custom function for form manipulation (user assign controls) (see workflow_addons.module)
    workflow_addons_node_form_user_additions($form, $form_state, $title, $name, $current, $choices, $timestamp, $comment);

In exchange, I'd be more than grateful if you could provide some feedback and tips on how to improve my code. I'm very keen to release it as a proper stand alone module. I would especially like a way to avoid hacking the workflow module to get my changes to work.

BTW, this code has been used in a production environment and is working well.

nancydru’s picture

Status: Active » Postponed (maintainer needs more info)

I can't really say I understand this but you may want to see if Rules can do this

nancydru’s picture

I will be committing this change for you, so you can do a proper form_alter()

function workflow_node_form(&$form, $form_state, $title, $name, $current, $choices, $timestamp = NULL, $comment = NULL) {
  // Give form_alters the chance to see the parameters.
  $form['#wf_options'] = array(
    'title' => $title,
    'name' => $name,
    'current' => $current,
    'choices' => $choices,
    'timestamp' => $timestamp,
    'comment' => $comment,
    );

Your Dropbox seems to be gone, so I can't see the extra function for a default comment.

nancydru’s picture

I'll also give you a chance to change the comment in workflow_execute_transition():

  // Let other modules modify the comment.
  $context = array(
    'node' => $node,
    'sid' => $sid,
    'old_sid' => $old_sid,
    );
  drupal_alter('workflow_comment', $comment, $context);

So, you can implement a hook_workflow_comment_alter() if you so desire. You may not alter the contents of the $context array, but have at it on the comment text.

nancydru’s picture

Status: Postponed (maintainer needs more info) » Closed (won't fix)

No further updates from posters.

johnv’s picture

Issue summary: View changes

ITMT, in version 7.x.2.x, a new hook_..._alter() is introduced, where you can programmatically change the permitted transitions based upon your own criteria.

But in this case, you'd like to add the 'reviewer' to the transition, not the node itself. Let's discuss that in #2262915: [META] Allow changing of Workflow Widget before displaying it.