Sitecore Workflows: Targeted Notifications

While working on my last project, the client has requested for notifications to be sent during workflow changes to specific people, either the creator of the page, to a list of people or to the person who reviewed, accepted, rejected etc… (taken an action in the workbox on) the new page. According to our knowledge, this is not supported in Sitecore and we’re not aware of such modules. We decided to take it into our own hands to create such functionality.

Notification Targets

We started by narrowing down the list of targets that we’ll be supporting. According to the requirements, we needed to notify three types of targets:

  1. A list of supervisors, managers, auditors etc… This would be implemented by entering a comma-separated list of email recipients to be notified. This is usually used when a page has been submitted for reviewal and all people responsible for reviewing will be notified.
  2. The original author/owner of the page. This would be implemented by notifying the owner of the page which is saved inside each Sitecore item by Sitecore itself. You would usually see under the ‘Quick Info’ of each item a field named ‘Item owner’ which would have a value such as ‘sitecore\Admin’. This is usually used when an action has been done by someone other than the owner that requires the owner’s attention.
  3. The specific supervisor, manager, auditor etc… that last took an action on the page using the workbox. This is usually used when more than two levels of management is used (e.g. author @ draft, auditor @pending audit, supervisor @ pending approval). When the supervisor rejects the page, I need the auditor that accepted the page to be notified without having to send to every single auditor. Implementing this is quite tricky and is not straight forward to do. We decided to notify the user (auditor for example) that last changed the workflow state from ‘pending audit’. In other words, I would define an auditor by a person who moved the workflow state from ‘pending audit’. So we needed to specify this somewhere by defining our notification action.

 

Sitecore Template

We started by creating a new template that inherits from ‘Action’ (sitecore/Templates/System/Workflow/Action). This inherited Action contains a ‘Type’ field which we should default to (by setting its value in our standard field) our new class implementation type (fullNamespace.ClassName, AssemblyName).

In order to implement the above requirements, we had to create three fields as follows:

  1. Email Recipients – Single-Line Text: Email Recipient(s) to send to as CC or To, if no other target type is specified, (comma separated)
  2. Notify Author – Checkbox: If checked, notifies the author of the item, ignoring the ‘From State’ field’s value
  3. From State – Droplink: Notifies the user that last moved the current item’s workflow state from the selected State. Source: query:./ancestor::*[@@templatename=’Workflow’]//*[@@templateid='{4B7E2DA9-DE43-4C83-88C3-02F042031D04}’] (which is the list of workflow states for the related workflow in which the notification action resides in)

In addition to the above, we created:

  1. Email Subject-Single-Line Text:  The subject of the email to be sent
  2. Email Body-Multi-Line Text: Email body that will be sent to the target user after replacing the tokens with their actual values

For more flexibility, we enabled the use of tokens inside the Email Body field:

  • Last Action By: ‘#actionBy#’
  • Previous State: ‘#prevState#’
  • New State: ‘#newState#’
  • Comments: ‘#comments#’
  • Current Content Item Name: ‘#itemName#’

 

Template

The way this will be used, is by simply creating an instance from this new template under any state/command which requires notifications. Sitecore will automatically execute actions under workflow states/commands in order.

NotificationExample1

Backend CODE

public void Process(WorkflowPipelineArgs args)
{
 var item = args.DataItem;

 var fromState = args.ProcessorItem.InnerItem.Fields[fromState_FieldName]?.Value;
 var notifyAuthor = args.ProcessorItem.InnerItem.Fields[notifyAuthor_FieldName]?.Value == "1";
 var emailBody = args.ProcessorItem.InnerItem.Fields[emailBody_FieldName]?.Value;
 var subject = args.ProcessorItem.InnerItem.Fields[emailSubject_FieldName]?.Value;
 var emailRec = args.ProcessorItem.InnerItem.Fields[additionalEmailRecipients_FieldName]?.Value;

 var sitecoreUserEmail = item.getTargetUserEmail(fromState, notifyAuthor);

 if (string.IsNullOrWhiteSpace(sitecoreUserEmail) && string.IsNullOrWhiteSpace(emailRec))
 {
   Sitecore.Diagnostics.Log.Warn("User has no email.", this);
   return;
 }

 var to = string.IsNullOrWhiteSpace(sitecoreUserEmail) ? emailRec : sitecoreUserEmail;
 var cc = string.IsNullOrWhiteSpace(sitecoreUserEmail) ? string.Empty : emailRec;

 EmailService emailService = new EmailService();
 var email = new EmailFormat() { EmailConfiguration = _emailConfig, EmailTemplate = emailBody, EmailTo = to, Subject = subject };

 emailService.SendEmail(email, getEmailTokens(item, args), cc);
}

private Dictionary<string, string> getEmailTokens(Item item, WorkflowPipelineArgs args)
{
  var actionBy = item.Statistics.UpdatedBy;
  var prevState = item.getLastActionOldState();
  var newState = item.getLastActionNewState();
  var comments = args.Comments;
  var itemName = item.DisplayName;

  return new Dictionary<string, string>() {
  { "#actionBy#", actionBy },
  { "#prevState#", prevState },
  { "#newState#", newState },
  { "#comments#", comments },
  { "#itemName#", itemName }
  };
 }

We moved most of the core workflow code into a new WorkflowExtensions class:

public static class WorkflowExtensions
{
 private static WorkflowEvent[] GetHistory(Item item)
 {
  var workflowProvider = (item.Database.WorkflowProvider as Sitecore.Workflows.Simple.WorkflowProvider);
  if (workflowProvider == null)
   return null;

  if (workflowProvider.HistoryStore == null)
   return null;

  return workflowProvider.HistoryStore.GetHistory(item);
 }

 public static string GetTargetUserEmail(this Item item, string fromState, bool notifyAuthor)
 {
  var workflowEvents = getHistory(item);
  var user = notifyAuthor ? item.Statistics.CreatedBy : workflowEvents.OrderBy(o => o.Date).LastOrDefault(w => w.OldState == fromState)?.User;
  return string.IsNullOrWhiteSpace(user) ? string.Empty : Sitecore.Security.Accounts.User.FromName(user, false)?.Profile?.Email;
 }

 public static string GetLastActionOldState(this Item item)
 {
  var workflowEvents = getHistory(item);
  return item.Database.GetItem(workflowEvents.LastOrDefault().OldState).DisplayName;
 }

 public static string GetLastActionNewState(this Item item)
 {
  var workflowEvents = getHistory(item);
  return item.Database.GetItem(workflowEvents.LastOrDefault().NewState).DisplayName;
 }
}

The tricky part about the implementation is located inside the GetTargetUserEmail() which retrieves the workflow history of the current item, orders them by date and gets the user that last changed the state from the specified state. The ‘From State’ could be retrieved from the ‘OldState’.

We also have the GetLastActionOldState and NewState that are used for the EmailBody tokens.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s