diff --git a/modules/workflow_notify/src/Form/WorkflowNotifySettingsForm.php b/modules/workflow_notify/src/Form/WorkflowNotifySettingsForm.php
new file mode 100644
index 0000000..0cfc54c
--- /dev/null
+++ b/modules/workflow_notify/src/Form/WorkflowNotifySettingsForm.php
@@ -0,0 +1,223 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\workflow_notify\Form\WorkflowNotifySettingsForm.
+ */
+
+namespace Drupal\workflow_notify\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+
+class WorkflowNotifySettingsForm extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'workflow_notify_settings_form';
+  }
+
+  public function buildForm(array $form, \Drupal\Core\Form\FormStateInterface $form_state, $workflow = NULL) {
+    // 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', [
+        '@name' => ucwords($workflow->name)
+        ]));
+
+      // Let's add some breadcrumbs.
+      workflow_admin_ui_breadcrumbs($workflow);
+
+      $noyes = [t('No'), t('Yes')];
+
+      $form['wid'] = ['#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 = [];
+      foreach ($workflow->states as $sid => $state) {
+        if (!$state->sysid) {
+          $states[$sid] = $state;
+        }
+      }
+      if (!$states) {
+        $form['error'] = [
+          '#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 = ['-1' => t('Author')] + user_roles(TRUE);
+
+      // The module_invoke_all function does not allow passing by reference.
+      $args = [
+        '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'] = [
+        '#title' => t('Set "From" address to'),
+        '#type' => 'radios',
+        '#options' => [
+          'site' => t('Site email address'),
+          'changer' => t('Current user'),
+        ],
+        '#default_value' => variable_get('workflow_notify_from_address_' . $workflow->wid, 'site'),
+        '#attributes' => [
+          'class' => [
+            '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 = [];
+      foreach (filter_formats() as $fid => $filter) {
+        $formats[$fid] = $filter->name;
+      }
+
+      $form['filter'] = [
+        '#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' => [
+          'class' => [
+            'container-inline'
+            ]
+          ],
+        '#description' => t('The message body will be processed using this filter format
+        in order to ensure security.'),
+      ];
+
+      $form['tokens'] = [
+        '#theme' => 'token_tree_link',
+        '#token_types' => [
+          'node',
+          'workflow',
+        ],
+      ];
+
+      // Column names for theme function.
+      $columns = [
+        'state' => t('State'),
+        'roles' => t('Roles to notify'),
+      ];
+
+      $args = ['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', []);
+
+      foreach ($states as $sid => $state) {
+        // Allow modules to insert state operations.
+        $state_links = module_invoke_all('workflow_operations', 'state', $workflow, $state);
+
+        $form['states'][$sid] = [
+          'state' => [
+            '#type' => 'markup',
+            '#markup' => filter_xss($state->state),
+          ],
+          'roles' => [
+            '#type' => 'checkboxes',
+            '#options' => $roles,
+            '#default_value' => (isset($notify[$sid]) ? $notify[$sid] : []),
+            '#attributes' => [
+              'class' => [
+                'state-roles'
+                ]
+              ],
+          ],
+          'subject' => [
+            '#type' => 'textarea',
+            '#rows' => 5,
+            '#default_value' => variable_get('workflow_notify_subject_' . $sid, '[node:title] is now "[node:field_workflow_state:new-state:label]"'),
+            '#attributes' => [
+              'class' => [
+                'subject'
+                ]
+              ],
+          ],
+          'body' => [
+            '#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' => [
+              'class' => [
+                'body'
+                ]
+              ],
+          ],
+        ];
+      }
+
+      $form['#tree'] = TRUE;
+      $form['submit'] = [
+        '#type' => 'submit',
+        '#value' => 'Save notifications settings',
+      ];
+
+      return $form;
+    }
+    else {
+      drupal_goto('admin/config/workflow/workflow');
+    }
+  }
+
+  public function submitForm(array &$form, \Drupal\Core\Form\FormStateInterface $form_state) {
+    $roles = variable_get('workflow_notify_roles', []);
+
+    $workflow = $form['#workflow'];
+
+    variable_set('workflow_notify_from_address_' . $workflow->wid, $form_state->getValue(['from_address']));
+    variable_set('workflow_notify_filter_format_' . $workflow->wid, $form_state->getValue(['filter']));
+
+    foreach ($form_state->getValue(['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.'));
+  }
+
+}
diff --git a/modules/workflow_notify/src/WorkflowNotifySettingsForm.php b/modules/workflow_notify/src/WorkflowNotifySettingsForm.php
new file mode 100644
index 0000000..bbe3922
--- /dev/null
+++ b/modules/workflow_notify/src/WorkflowNotifySettingsForm.php
@@ -0,0 +1,213 @@
+<?php
+namespace Drupal\workflow_notify;
+
+class WorkflowNotifySettingsForm extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'workflow_notify_settings_form';
+  }
+
+  public function buildForm(array $form, \Drupal\Core\Form\FormStateInterface $form_state, $workflow = NULL) {
+    // 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', [
+        '@name' => ucwords($workflow->name)
+        ]));
+
+      // Let's add some breadcrumbs.
+      workflow_admin_ui_breadcrumbs($workflow);
+
+      $noyes = [t('No'), t('Yes')];
+
+      $form['wid'] = ['#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 = [];
+      foreach ($workflow->states as $sid => $state) {
+        if (!$state->sysid) {
+          $states[$sid] = $state;
+        }
+      }
+      if (!$states) {
+        $form['error'] = [
+          '#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 = ['-1' => t('Author')] + user_roles(TRUE);
+
+      // The module_invoke_all function does not allow passing by reference.
+      $args = [
+        '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'] = [
+        '#title' => t('Set "From" address to'),
+        '#type' => 'radios',
+        '#options' => [
+          'site' => t('Site email address'),
+          'changer' => t('Current user'),
+        ],
+        '#default_value' => variable_get('workflow_notify_from_address_' . $workflow->wid, 'site'),
+        '#attributes' => [
+          'class' => [
+            '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 = [];
+      foreach (filter_formats() as $fid => $filter) {
+        $formats[$fid] = $filter->name;
+      }
+
+      $form['filter'] = [
+        '#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' => [
+          'class' => [
+            'container-inline'
+            ]
+          ],
+        '#description' => t('The message body will be processed using this filter format
+        in order to ensure security.'),
+      ];
+
+      $form['tokens'] = [
+        '#theme' => 'token_tree_link',
+        '#token_types' => [
+          'node',
+          'workflow',
+        ],
+      ];
+
+      // Column names for theme function.
+      $columns = [
+        'state' => t('State'),
+        'roles' => t('Roles to notify'),
+      ];
+
+      $args = ['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', []);
+
+      foreach ($states as $sid => $state) {
+        // Allow modules to insert state operations.
+        $state_links = module_invoke_all('workflow_operations', 'state', $workflow, $state);
+
+        $form['states'][$sid] = [
+          'state' => [
+            '#type' => 'markup',
+            '#markup' => filter_xss($state->state),
+          ],
+          'roles' => [
+            '#type' => 'checkboxes',
+            '#options' => $roles,
+            '#default_value' => (isset($notify[$sid]) ? $notify[$sid] : []),
+            '#attributes' => [
+              'class' => [
+                'state-roles'
+                ]
+              ],
+          ],
+          'subject' => [
+            '#type' => 'textarea',
+            '#rows' => 5,
+            '#default_value' => variable_get('workflow_notify_subject_' . $sid, '[node:title] is now "[node:field_workflow_state:new-state:label]"'),
+            '#attributes' => [
+              'class' => [
+                'subject'
+                ]
+              ],
+          ],
+          'body' => [
+            '#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' => [
+              'class' => [
+                'body'
+                ]
+              ],
+          ],
+        ];
+      }
+
+      $form['#tree'] = TRUE;
+      $form['submit'] = [
+        '#type' => 'submit',
+        '#value' => 'Save notifications settings',
+      ];
+
+      return $form;
+    }
+    else {
+      drupal_goto('admin/config/workflow/workflow');
+    }
+  }
+
+  public function submitForm(array &$form, \Drupal\Core\Form\FormStateInterface $form_state) {
+    $roles = variable_get('workflow_notify_roles', []);
+
+    $workflow = $form['#workflow'];
+
+    variable_set('workflow_notify_from_address_' . $workflow->wid, $form_state->getValue(['from_address']));
+    variable_set('workflow_notify_filter_format_' . $workflow->wid, $form_state->getValue(['filter']));
+
+    foreach ($form_state->getValue(['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.'));
+  }
+
+}
diff --git a/modules/workflow_notify/workflow_notify.admin.inc b/modules/workflow_notify/workflow_notify.admin.inc
new file mode 100644
index 0000000..3e30512
--- /dev/null
+++ b/modules/workflow_notify/workflow_notify.admin.inc
@@ -0,0 +1,295 @@
+<?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.
+    // @FIXME
+// drupal_set_title() has been removed. There are now a few ways to set the title
+// dynamically, depending on the situation.
+// 
+// 
+// @see https://www.drupal.org/node/2067859
+// 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 (\Drupal::moduleHandler()->getImplementations('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.
+    // @FIXME
+// // @FIXME
+// // The correct configuration object could not be determined. You'll need to
+// // rewrite this call manually.
+// $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;
+    }
+
+    // @FIXME
+// // @FIXME
+// // The correct configuration object could not be determined. You'll need to
+// // rewrite this call manually.
+// $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 (\Drupal::moduleHandler()->getImplementations('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');
+
+    // @FIXME
+// Could not extract the default value because it is either indeterminate, or
+// not scalar. You'll need to provide a default value in
+// config/install/workflow_notify.settings.yml and config/schema/workflow_notify.schema.yml.
+$notify = \Drupal::config('workflow_notify.settings')->get('workflow_notify_roles');
+
+    foreach ($states as $sid => $state) {
+      // Allow modules to insert state operations.
+      $state_links = \Drupal::moduleHandler()->invokeAll('workflow_operations', ['state', $workflow, $state]);
+
+      // @FIXME
+// // @FIXME
+// // The correct configuration object could not be determined. You'll need to
+// // rewrite this call manually.
+// $form['states'][$sid] = array(
+//         'state' => array(
+//           '#type' => 'markup',
+//           '#markup' => \Drupal\Component\Utility\Xss::filter($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::service("renderer")->render($form['from_address']);
+  $output .= \Drupal::service("renderer")->render($form['filter']);
+  $output .= \Drupal::service("renderer")->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 (\Drupal\Core\Render\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::service("renderer")->render($form['states'][$id][$field]),
+        'class' => array(\Drupal\Component\Utility\Html::getClass($field)),
+        );
+    }
+
+    // Put the data row into the table.
+    $table['rows'][] = array('data' => $row);
+  }
+
+  // @FIXME
+// theme() has been renamed to _theme() and should NEVER be called directly.
+// Calling _theme() directly can alter the expected output and potentially
+// introduce security issues (see https://www.drupal.org/node/2195739). You
+// should use renderable arrays instead.
+// 
+// 
+// @see https://www.drupal.org/node/2195739
+// $output .= theme('table', $table);
+
+
+  $output .= \Drupal::service("renderer")->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) {
+  // @FIXME
+// Could not extract the default value because it is either indeterminate, or
+// not scalar. You'll need to provide a default value in
+// config/install/workflow_notify.settings.yml and config/schema/workflow_notify.schema.yml.
+$roles = \Drupal::config('workflow_notify.settings')->get('workflow_notify_roles');
+
+  $workflow = $form['#workflow'];
+
+  // @FIXME
+// // @FIXME
+// // The correct configuration object could not be determined. You'll need to
+// // rewrite this call manually.
+// variable_set('workflow_notify_from_address_' . $workflow->wid , $form_state['values']['from_address']);
+
+  // @FIXME
+// // @FIXME
+// // The correct configuration object could not be determined. You'll need to
+// // rewrite this call manually.
+// 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]);
+    }
+
+    // @FIXME
+// // @FIXME
+// // The correct configuration object could not be determined. You'll need to
+// // rewrite this call manually.
+// variable_set("workflow_notify_subject_$sid", $values['subject']);
+
+    // @FIXME
+// // @FIXME
+// // The correct configuration object could not be determined. You'll need to
+// // rewrite this call manually.
+// variable_set("workflow_notify_body_$sid", $values['body']);
+
+  }
+
+  \Drupal::configFactory()->getEditable('workflow_notify.settings')->set('workflow_notify_roles', $roles)->save();
+
+  drupal_set_message(t('The notification settings have been saved.'));
+}
diff --git a/modules/workflow_notify/workflow_notify.info.yml b/modules/workflow_notify/workflow_notify.info.yml
new file mode 100644
index 0000000..b23be17
--- /dev/null
+++ b/modules/workflow_notify/workflow_notify.info.yml
@@ -0,0 +1,7 @@
+name: 'Workflow Notify'
+description: 'Notify roles of Workflow transitions.'
+dependencies:
+  - workflow
+core: 8.x
+package: Workflow
+type: module
diff --git a/modules/workflow_notify/workflow_notify.module b/modules/workflow_notify/workflow_notify.module
new file mode 100644
index 0000000..b69e90f
--- /dev/null
+++ b/modules/workflow_notify/workflow_notify.module
@@ -0,0 +1,292 @@
+<?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().
+ *
+ * @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) {
+  $user = \Drupal::currentUser();
+  switch ($op) {
+    // React to a transition after it's done.
+    case 'transition post':
+      // See if this is a state that we notify for.
+      // @FIXME
+// Could not extract the default value because it is either indeterminate, or
+// not scalar. You'll need to provide a default value in
+// config/install/workflow_notify.settings.yml and config/schema/workflow_notify.schema.yml.
+$notify = \Drupal::config('workflow_notify.settings')->get('workflow_notify_roles');
+
+      if (isset($notify[$new_state])) {
+        // List of content types.
+        $node_types = node_type_get_names();
+
+        // The name of the person making the change.
+        $changer = format_username($user);
+        $changer_mail = $user->mail;
+
+        list($entity_id, , $entity_bundle) = entity_extract_ids($entity_type, $entity);
+
+        // Okay, we are notifying someone of this change.
+        // So let's get the workflow object.
+        /* @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 = \Drupal::entityManager()->getStorage('user');
+
+        // 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 (\Drupal::moduleHandler()->getImplementations('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)) {
+          // @FIXME
+// l() expects a Url object, created from a route name or external URI.
+// 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 . '>';
+        }
+
+        // @FIXME
+// // @FIXME
+// // The correct configuration object could not be determined. You'll need to
+// // rewrite this call manually.
+// $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.
+        // @FIXME
+// // @FIXME
+// // The correct configuration object could not be determined. You'll need to
+// // rewrite this call manually.
+// $params['context']['subject'] = variable_get("workflow_notify_subject_$new_state",
+//           '[node:title] is now "[workflow:workflow-current-state-name]"');
+
+
+        // @FIXME
+// // @FIXME
+// // The correct configuration object could not be determined. You'll need to
+// // rewrite this call manually.
+// $params['context']['body'] = variable_get("workflow_notify_body_$new_state",
+//           '<a href="[node:url:absolute]">[node:title]</a> is now "@state".');
+
+
+        // @FIXME
+// // @FIXME
+// // The correct configuration object could not be determined. You'll need to
+// // rewrite this call manually.
+// 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;
+  }
+}
+
+/**
+ * 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'] = \Drupal\Component\Utility\Xss::filter(\Drupal::token()->replace($params['context']['subject'], $params['data'], $params));
+      $message['body'][] = check_markup(\Drupal::token()->replace($params['context']['body'], $params['data'], $params), $filter);
+
+      // @FIXME
+// l() expects a Url object, created from a route name or external URI.
+// 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;
+  }
+}
diff --git a/modules/workflow_notify/workflow_notify.permissions.yml b/modules/workflow_notify/workflow_notify.permissions.yml
new file mode 100644
index 0000000..e8947d4
--- /dev/null
+++ b/modules/workflow_notify/workflow_notify.permissions.yml
@@ -0,0 +1,3 @@
+'workflow notify':
+  title: 'Receive workflow notifications'
+  description: 'The user may be notified of a workflow state change.'
diff --git a/modules/workflow_notify/workflow_notify.routing.yml b/modules/workflow_notify/workflow_notify.routing.yml
new file mode 100644
index 0000000..c8cc0af
--- /dev/null
+++ b/modules/workflow_notify/workflow_notify.routing.yml
@@ -0,0 +1,7 @@
+workflow_notify.settings_form:
+  path: '/admin/config/workflow/workflow/notify/{workflow}'
+  defaults:
+    _title: Notifications
+    _form: \Drupal\workflow_notify\Form\WorkflowNotifySettingsForm
+  requirements:
+    _permission: 'administer workflow'
diff --git a/modules/workflow_notify/workflow_notify_og/workflow_notify_og.info.yml b/modules/workflow_notify/workflow_notify_og/workflow_notify_og.info.yml
new file mode 100644
index 0000000..ddbd196
--- /dev/null
+++ b/modules/workflow_notify/workflow_notify_og/workflow_notify_og.info.yml
@@ -0,0 +1,7 @@
+name: 'Workflow Notify OG'
+description: 'Notify roles by OG groups of Workflow transitions.'
+dependencies:
+  - workflow_notify
+core: 8.x
+package: Workflow
+type: module
diff --git a/modules/workflow_notify/workflow_notify_og/workflow_notify_og.install b/modules/workflow_notify/workflow_notify_og/workflow_notify_og.install
new file mode 100644
index 0000000..e1bc009
--- /dev/null
+++ b/modules/workflow_notify/workflow_notify_og/workflow_notify_og.install
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the Workflow module.
+ *
+ */
+
+/**
+ * Implements hook_install().
+ */
+function workflow_notify_og_install() {
+  // @FIXME
+// 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() {
+  // @FIXME
+// db_update('system')
+//     ->fields(array('weight' => -1))
+//     ->condition('name', 'workflow_notify_og')
+//     ->condition('type', 'module')
+//     ->execute();
+
+}
diff --git a/modules/workflow_notify/workflow_notify_og/workflow_notify_og.module b/modules/workflow_notify/workflow_notify_og/workflow_notify_og.module
new file mode 100644
index 0000000..2975d67
--- /dev/null
+++ b/modules/workflow_notify/workflow_notify_og/workflow_notify_og.module
@@ -0,0 +1,134 @@
+<?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':
+      // @FIXME
+// // @FIXME
+// // This looks like another module's variable. You'll need to rewrite this call
+// // to ensure that it uses the correct configuration object.
+// $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, \Drupal\Core\Form\FormStateInterface &$form_state, $form_id) {
+  switch($form_id) {
+    case 'workflow_notify_settings_form':
+      // @FIXME
+// // @FIXME
+// // This looks like another module's variable. You'll need to rewrite this call
+// // to ensure that it uses the correct configuration object.
+// $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'];
+  // @FIXME
+// // @FIXME
+// // This looks like another module's variable. You'll need to rewrite this call
+// // to ensure that it uses the correct configuration object.
+// $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.
+  // @FIXME
+// // @FIXME
+// // This looks like another module's variable. You'll need to rewrite this call
+// // to ensure that it uses the correct configuration object.
+// variable_set('workflow_notify_og', $limit);
+
+
+  $form_state['redirect'] = "admin/config/workflow/workflow/$workflow->wid";
+}
