--- og_workflow.install	2012-03-29 13:09:13.000000000 +0200
+++ og_workflow.install	2012-06-06 00:20:06.453080414 +0200
@@ -35,3 +35,22 @@
   );
   return $schema;
 }
+
+/**
+ * Store roles in a comma separated string and use 'author' instead of 'a'
+ * just like the workflow module does.
+ */
+function og_workflow_update_7200() {
+  $transitions = db_select('og_workflow_transitions', 'owt')
+      ->fields('owt')
+      ->execute();
+  while ($transition = $transitions->fetchObject()) {
+    $roles = str_split($transition->roles);
+    $roles = str_replace('a', 'author', $roles);
+    $roles = implode(',', $roles);
+    db_update('og_workflow_transitions')
+      ->fields(array('roles' => $roles))
+      ->condition('tid', $transition->tid)
+      ->execute();
+  }
+}

--- og_workflow.module	2012-03-29 13:09:13.000000000 +0200
+++ og_workflow.module	2012-06-06 01:31:58.788991918 +0200
@@ -1,4 +1,9 @@
 <?php
+
+/**
+ * Move our implementation of hook_form_alter() to the end of the processing
+ * list.
+ */
 function og_workflow_module_implements_alter(&$implementations, $hook) {
   if ($hook == 'form_alter') {
     $group = $implementations['og_workflow'];
@@ -7,6 +12,11 @@
   }
 }
 
+
+/**
+ * Register theme_og_workflow_admin_form() to be called for rendering the admin
+ * form.
+ */
 function og_workflow_theme() {
   return array(
     'og_workflow_admin_form' => array(
@@ -15,6 +25,58 @@
   );
 }
 
+
+/**
+ * Remove the path 'workflow/workflow/edit' defined by the workflow module from
+ * the menu system.
+ * Why?
+ */
+function og_workflow_menu_alter(&$items) {
+  unset($items['admin/config/workflow/workflow/edit']);
+}
+
+
+/**
+ * Define the paths for the menu system.
+ * What is "/edit/%/edit" for?
+ */
+function og_workflow_menu() {
+  $items['admin/config/workflow/workflow/edit/%'] = array(
+    'title' => 'Edit workflow',
+    'access arguments' => array('administer workflow'),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('workflow_admin_ui_edit_form', 5),
+  );
+  $items['admin/config/workflow/workflow/edit/%/edit'] = array(
+    'title' => 'Edit workflow',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => 0,
+  );
+  $items['admin/config/workflow/workflow/edit/%/og'] = array(
+    'title' => 'Edit OG workflow',
+    'type' => MENU_LOCAL_TASK,
+    'access arguments' => array('administer workflow'),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('og_workflow_admin_form', 5),
+    'weight' => 10,
+  );
+  /* waiting for workflow's better path menu hierarchy
+  $items['admin/config/workflow/workflow/%/og'] = array(
+    'title' => 'Edit organic groups workflow',
+    //'type' => MENU_LOCAL_TASK,
+    //'context' => MENU_CONTEXT_INLINE,
+    'access arguments' => array('administer workflow'),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('og_workflow_admin_form', 4),
+  );
+  */
+  return $items;
+}
+
+
+/**
+ * Theme function for rendering the admin form.
+ */
 function theme_og_workflow_admin_form($variables) {
   $form = $variables['form'];
   $wid = $form['wid']['#value'];
@@ -23,7 +85,7 @@
     $output = drupal_render($form['wf_name']);
     $states = workflow_get_workflow_states_by_wid($wid, array('status' => 1));
     if ($states) {
-      $roles = og_workflow_roles();
+      $roles = og_workflow_all_roles();
       $header = array(array('data' => t('From / To') . ' &nbsp;' . WORKFLOW_ARROW));
       $rows = array();
       foreach ($states as $state) {
@@ -67,46 +129,12 @@
   }
 }
 
-function og_workflow_menu_alter(&$items) {
-  unset($items['admin/config/workflow/workflow/edit']);
-}
-
-function og_workflow_menu() {
-  $items['admin/config/workflow/workflow/edit/%'] = array(
-    'title' => 'Edit workflow',
-    'access arguments' => array('administer workflow'),
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('workflow_admin_ui_edit_form', 5),
-  );
-  $items['admin/config/workflow/workflow/edit/%/edit'] = array(
-    'title' => 'Edit workflow',
-    'type' => MENU_DEFAULT_LOCAL_TASK,
-    'weight' => 0,
-  );
-
-  $items['admin/config/workflow/workflow/edit/%/og'] = array(
-    'title' => 'Edit OG workflow',
-    'type' => MENU_LOCAL_TASK,
-    'access arguments' => array('administer workflow'),
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('og_workflow_admin_form', 5),
-    'weight' => 10,
-  );
-  /* waiting for workflow's better path menu hierarchy
-  $items['admin/config/workflow/workflow/%/og'] = array(
-    'title' => 'Edit organic groups workflow',
-    //'type' => MENU_LOCAL_TASK,
-    //'context' => MENU_CONTEXT_INLINE,
-    'access arguments' => array('administer workflow'),
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('og_workflow_admin_form', 4),
-  );
-  */
-  return $items;
-}
 
+/**
+ * The admin page callback that returns the og workflow admin form for a
+ * specific workflow; called by 'admin/config/workflow/workflow/edit/%/og'.
+ */
 function og_workflow_admin_form($form, $form_state, $wid) {
-  // If we don't have a wid by this point, we need to go back to creating one at admin/config/workflow/workflow/add
   if ($workflow = workflow_get_workflows_by_wid($wid)) {
     drupal_set_title(t('Edit workflow %name for organic groups roles', array('%name' => $workflow->name)), PASS_THROUGH);
 
@@ -136,42 +164,20 @@
     );
     return $form;
   }
+  // If we don't have a wid by this point, we need to go back to creating one
+  // at admin/config/workflow/workflow/add
   else {
     drupal_goto('admin/config/workflow/workflow/add');
   }
 }
 
-function og_workflow_admin_form_submit($form, &$form_state) {
-  $transitions = $form_state['values']['transitions'];
-  if (!$transitions) {
-    return;
-  }
-  foreach ($transitions as $from => $to_data) {
-    foreach ($to_data as $to => $role_data) {
-      $roles = '';
-      foreach ($role_data as $role => $can_do) {
-        if ($can_do) {
-          $roles .= $role;
-        }
-      }
-//      $workflow_transition = workflow_get_workflow_transitions_by_sid_target_sid($from, $to);
-      $record = array(
-//        'tid' => $workflow_transition->tid,
-        'sid' => $from,
-        'target_sid' => $to,
-        'roles' => $roles
-      );
-      db_merge('og_workflow_transitions')->insertFields($record)->updateFields(array('roles' => $roles))->condition(db_and()->condition('sid', $from)->condition('target_sid', $to))->execute();
-    }
-  }
-
-  drupal_set_message(t('The workflow was updated.'));
-//  $form_state['redirect'] = 'admin/config/workflow/workflow';
-}
 
+/**
+ * The actual checkbox grid for the admin form.
+ * (Copied from workflow/workflow_admin_ui/workflow_admin_ui.module)
+ */
 function og_workflow_admin_transition_grid_form($wid) {
   $form = array();
-  $roles = og_workflow_roles();
   $states = workflow_get_workflow_states_by_wid($wid, array('status' => 1));
   if (!$states) {
     $form = array(
@@ -180,6 +186,7 @@
     );
     return $form;
   }
+  $roles = og_workflow_all_roles($wid);
   foreach ($states as $state1) {
     $state_id = $state1->sid;
     $name = $state1->state;
@@ -191,170 +198,250 @@
         continue;
       }
       if ($nested_state_id != $state_id) {
-        // Need to generate checkboxes for transition from $state to $nested_state.
+        // Generate checkboxes for transition from $state to $nested_state.
         $from = $state_id;
         $to = $nested_state_id;
-        $transition = og_workflow_get_transitions($from, $to);
-          foreach ($roles as $rid => $role_name) {
-            $checked = FALSE;
-            if ($transition) {
-              if (strpos($transition->roles, (string)$rid) !== FALSE) {
-                $checked = TRUE;
-              }
+        $transition = og_workflow_get_transition($from, $to);
+        foreach ($roles as $rid => $role_name) {
+          $checked = FALSE;
+          if ($transition) {
+            if (in_array($rid, $transition->roles)) {
+              $checked = TRUE;
             }
-            $form[$from][$to][$rid] = array(
-              '#type' => 'checkbox',
-              '#title' => check_plain($role_name),
-              '#default_value' => $checked,
-            );
           }
-
+          $form[$from][$to][$rid] = array(
+            '#type' => 'checkbox',
+            '#title' => check_plain($role_name),
+            '#default_value' => $checked,
+          );
+        }
       }
     }
   }
   return $form;
 }
 
-function og_workflow_get_transitions($sid, $target_sid) {
-  $results = db_query('SELECT * FROM {og_workflow_transitions} WHERE sid = :sid AND target_sid = :target_sid', array(':sid' => $sid, ':target_sid' => $target_sid));
-  if ($results->rowCount()) {
-    return $results->fetchObject();
-  }
-  else {
-    return FALSE;
-  }
-}
 
-function og_workflow_roles() {
-  static $roles = NULL;
-  if (!$roles) {
-    $roles = array('a' => 'author');
-    foreach (og_roles() as $rid => $name) {
-      $roles[$rid] = check_plain($name);
+/**
+ * Process the data returned by the og workflow admin form and merge it into
+ * the database.
+ */
+function og_workflow_admin_form_submit($form, &$form_state) {
+  $transitions = $form_state['values']['transitions'];
+  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[] = $role;
+        }
+      }
+      $roles = implode(',', $roles);
+      db_merge('og_workflow_transitions')
+        ->key(array('sid' => $from,
+                    'target_sid' => $to))
+        ->fields(array('roles' => $roles))
+        ->execute();
     }
   }
-  return $roles;
+  drupal_set_message(t('The workflow was updated.'));
 }
 
+
+/**
+ * Alter the workflow forms for nodes and comments.
+ * Why do all the checks and not just check for the existence of the workflow form?
+ */
 function og_workflow_form_alter(&$form, &$form_state, $form_id) {
-  if ((isset($form['#node']) && $form_id == 'comment_node_' . $form['#node']->type . '_form')
-    || (isset($form['#node']->type) && isset($form['#node']) && $form['#node']->type . '_node_form' == $form_id)) {
-    // Skip if there are no workflows.
-    if (isset($form['#node'])) {
-      $node = $form['#node'];
-      // Abort if user does not want to display workflow form on node editing form.
-      if (!in_array('node', variable_get('workflow_' . $form['#node']->type, array('node')))) {
-        return;
-      }
+  if ((isset($form['#node']) && isset($form['#node']->type))
+     && ($form_id == 'comment_node_' . $form['#node']->type . '_form'
+      || $form_id == $form['#node']->type . '_node_form')) {
+    $node = $form['#node'];
+    // Abort if user does not want to display workflow form on node editing form.
+    if (!in_array('node', variable_get('workflow_' . $node->type, array('node')))) {
+      return;
     }
-    else {
-      $node = node_load($form['nid']['#value']);
-      $type = $node->type;
-      // Abort if user does not want to display workflow form on node editing form.
-      if (!in_array('comment', variable_get('workflow_' . $type, array('node')))) {
-        return;
-      }
+    // Abort if user does not want to display workflow form on comment editing form.
+    if (!in_array('comment', variable_get('workflow_' . $node->type, array('node')))) {
+      return;
     }
+    // Add the workflow form if there is at least one workflow for the current node type.
     if ($workflow = workflow_get_workflow_type_map_by_type($node->type)) {
       $form['workflow'] = og_workflow_node_form($node, $form);
     }
   }
 }
 
+
+/**
+ * Alter the workflow form on the workflow tab (@see og_workflow_node_form()).
+ */
 function og_workflow_form_workflow_tab_form_alter(&$form, &$form_state, $form_id) {
-  $node = $form['node']['#value'];
-  $form['workflow'] = og_workflow_node_form($node, $form);
+  if (!isset($form['node'])) {
+    drupal_set_message(t("No workflow transitions available for this node."));
+  }
+  else {
+    $node = $form['node']['#value'];
+    $form['workflow'] = og_workflow_node_form($node, $form);
+  }
 }
 
+
+/**
+ * Modify the workflow form for the workflow tab and the
+ * node form as well (that's why it's separate).
+ */
 function og_workflow_node_form($node, $form) {
   global $user;
-
+  // Don't modify the form for the admin user.
   if ($user->uid == 1) {
     return $form['workflow'];
   }
-
-  $ogs = array();
-  if (isset($node->group_audience)) {
-    $ogs = $node->group_audience['und'];
-  }
-  elseif (isset($form['group_audience'])) {
+  
+  // Why?
+  if (isset($form['group_audience'])) {
     return;
   }
-  
-  if (isset($form['#wf']) && !empty($ogs)) {
+    
+  // If there is a workflow for this node and there are groups, adjust the form.
+  if (isset($form['#wf'])) {
     $options = $form['workflow'][$form['#wf']->name]['#options'];
     $from_sid = $form['workflow'][$form['#wf']->name]['#default_value'];
     foreach ($options as $to_sid => $state) {
-      $transition = og_workflow_get_transitions($from_sid, $to_sid);
-      if ($transition) {
-        $allowed = FALSE;
-
-        if (isset($node->uid)) {
-          if ($node->uid == $user->uid) {
-            $rid = 'a';
-            if (strpos($transition->roles, (string)$rid) !== FALSE) {
-              $allowed = TRUE;
-            }
-          }
-        }
-
-        foreach ($ogs as $og) {
-          $rids = og_get_user_roles($og['gid']);
-          foreach($rids as $rid) {
-            if (strpos($transition->roles, (string)$rid) !== FALSE) {
-              $allowed = TRUE;
-            }
-          }
-        }
-        if ($allowed == FALSE) {
-          unset($form['workflow'][$form['#wf']->name]['#options'][$to_sid]);
-        }
+      // Remove option if user is prohibited to perform it.
+      if (!og_workflow_user_may_perform_transition($from_sid, $to_sid, $node)) {
+        unset($form['workflow'][$form['#wf']->name]['#options'][$to_sid]);
       }
     }
   }
   return $form['workflow'];
 }
 
-function og_workflow_workflow($op, $old_state, $new_state, $node) {
+
+/**
+ * Implements hook_workflow().
+ *
+ * If the current node is in a group, only allow to perform the transition if
+ * group based permissions are given.
+ *
+ * Delete according permissions if a workflow state is deleted.
+ */
+function og_workflow_workflow($op, $from_state, $to_state, $node) {
   switch ($op) {
     case 'transition pre':
-      global $user;
       if ($workflow = workflow_get_workflow_type_map_by_type($node->type)) {
-        if ($user->uid == 1) {
+        if (og_workflow_user_may_perform_transition($from_state, $to_state, $node)) {
           return TRUE;
         }
-        $transition = og_workflow_get_transitions($old_state, $new_state);
-        if (isset($node->group_audience) && $transition) {
-          $ogs = $node->group_audience['und'];
-          if (isset($node->uid)) {
-            if ($node->uid == $user->uid) {
-              $rid = 'a';
-              if (strpos($transition->roles, (string)$rid) !== FALSE) {
-                return TRUE;
-              }
-            }
-          }
-
-          foreach ($ogs as $og) {
-            $rids = og_get_user_roles($og['gid']);
-            foreach($rids as $rid) {
-              if (strpos($transition->roles, (string)$rid) !== FALSE) {
-                return TRUE;
-              }
-            }
-          }
-
+        else {
           drupal_set_message(t('You are not allowed to make this transition.'));
           return FALSE;
-
         }
       }
       break;
-    case 'transition post':
-      break;
     case 'state delete':
-      db_delete('og_workflow_transitions')->condition(db_or()->condition('sid', $old_state)->condition('target_sid', $old_state))->execute();
+      db_delete('og_workflow_transitions')
+        ->condition(db_or()->condition('sid', $from_state)->condition('target_sid', $from_state))
+        ->execute();
       break;
   }
 }
 
+
+/**
+ * Get the roles that are be available in all groups.
+ */
+function og_workflow_all_roles() {
+  static $roles = NULL;
+  if (!$roles) {
+    $roles = array('author' => 'author');
+    $group_bundles = og_get_all_group_bundle();
+    foreach ($group_bundles as $entity_type => $bundles) {
+      foreach ($bundles as $bundle_name => $bundle_label) {
+        $bundle_roles = og_roles($entity_type, $bundle_name);
+        foreach ($bundle_roles as $rid => $name) {
+          $roles[$rid] = $bundle_label . " – " . $name;
+        }
+      }
+    }
+  }
+  return $roles;
+}
+
+
+/**
+ * Get the roles of a user for the groups that the given node belongs to.
+ */
+function og_workflow_user_roles_for_node($node, $user=NULL) {
+  if ($user == NULL) {
+    global $user;
+  }
+  $roles = array();
+  $audience_fields = og_get_group_audience_fields('node', $node->type);
+  if (!empty($audience_fields)) {
+    $audience_field_names = array_keys($audience_fields);
+    $audience_field_name = $audience_field_names[0]; // assume we have only one group field
+    $target_groups = $node->$audience_field_name;
+    $target_groups = $target_groups['und'];
+    foreach ($target_groups as $group) {
+      $audience_group_node = $group['target_id'];
+      $og_role_ids = array_keys(og_get_user_roles('node', $audience_group_node, $user->uid));
+      $roles = array_merge($roles, $og_role_ids);
+    }
+  }
+  return $roles;
+}
+
+
+/**
+ * Check if a user (defaults to the current) is allowed to move a given node
+ * from one state to another.
+ */
+function og_workflow_user_may_perform_transition($from_sid, $to_sid, $node, $user=NULL) {
+  if ($user == NULL) {
+    global $user;
+  }
+  if ($user->uid == 1) {
+    return TRUE;
+  }
+  $transition = og_workflow_get_transition($from_sid, $to_sid);
+  if ($transition) {
+    // If the current user is the author and author is allowed to perform
+    // the transition, return TRUE
+    if (isset($node->uid)) {
+      if ($node->uid == $user->uid) {
+        if (in_array('author', $transition->roles)) {
+          return TRUE;
+        }
+      }
+    }
+    // Allow transition if one of the roles the current user has in any
+    // of the groups of the current node has the permission.
+    $user_roles = og_workflow_user_roles_for_node($node, $user);
+    foreach($user_roles as $rid) {
+      if (in_array($rid, $transition->roles)) {
+        return TRUE;
+      }
+    }
+  }
+  return FALSE;
+}
+
+
+/**
+ * Retrieve the transition between two given states from the database.
+ */
+function og_workflow_get_transition($sid, $target_sid) {
+  $result = db_select('og_workflow_transitions', 'owt')
+      ->fields('owt')
+      ->condition('sid', $sid)
+      ->condition('target_sid', $target_sid)
+      ->execute();
+  $transition = $result->fetchObject();
+  if ($transition)
+    $transition->roles = explode(',', $transition->roles);
+  return $transition;
+}
