In almost all the Sitecore projects, developers have to create, sometimes customize and need to apply workflow to items. This guide covers all the pos...
Meghan Trainor's smashing hit arranged for wind band. This is the Horn 2 part - look for more!Full description
ALL ABOUT THAT BASSSFull description
Meghan Trainor's smashing hit arranged for wind band. This is the Horn 1 part - look for more!Full description
Full description
All about the hair
Cash and Cash Equivalents Problems
A magazine about history
Full description
Descripción: All About Chakras & Related Info
sheet music for read all about it - Emeli SandeDescripción completa
All about Halloween. Actvity for teenagers.
angels
Descrição completa
Full description
Full description
All About BritainDescripción completa
All About Xrd, SpektroskopiFull description
All about customizing workflow in Sitecore DEVELOPER S REFERENCE GUIDE ’
By Surendra Sharma TECHNICAL ARCHITECT | HTTP://SURENDRASHARMADOTNET.BLOGSPOT.IN/
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE In almost all the Sitecore Sit ecore projects, developers have to create, sometimes customize and need to apply workflow to items. This guide covers all the possible scenarios and customization technic with code used for workflow. It includes what is workflow, how to create custom workflow, customizing comment window, customizing workbox and customizing items in workbox. What is workflow? According to Sitecore “Workflows “ Workflows ensure that items move through a predefined set of states before they become publishable, usually intended to ensure that content receives the appropriate reviews and approvals before public ation to the live Web site.” site. ” All workflow are resides in “/sitecore/system/Workflows/ ” folder. A workflow have states, commands and actions. Every workflow start with draft state and end with publish or fi nal state. Most of the workflow looks indicated in below diagram
Let’s deep dive into understanding Sitecore workflow with one real life requirement and its implementation. Requirement: Client will need the following roles: Editor – Can edit content but cannot publish content. Reviewer – Can edit and publish content An editor needs to be able to assign content to an individual reviewer individual reviewer to review and publish. Below is a typical scenario: Editor A creates/updates A creates/updates to an item for example product item. Editor A saves the item content then assigns it to Reviewer A to review and publish.
1|Page
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE Reviewer A gets an email that Editor A wants a product reviewed and published. Reviewer A reviews the product detail and publishes it. Other reviewers can only view the items those are waiting state for approval but can’t take any action on items however admin can take any action along with Reviewer A.
The scenario above will be the most common scenario for reviewing and publishing content. For a multilingual website, client need language groups with scenario: Editor X in Germany makes an update to a G erman-language product. Editor X saves the content but isn't sure what individual reviewer to assign it to. Editor X thus assigns it to the German Review Group to review and publi sh. The German Review Group includes several members of the German marketing team (Reviewer X, Reviewer Y and Reviewer Z).
2|Page
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE
Reviewer X, Reviewer Y and Reviewer Z each get an email that a piece of content is available for review. Reviewer Z agrees to review the content and publish it.
3|Page
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE Solution:
We will implement this thi s requirement with below steps 1. Create Editor and Reviewer Roles 2. Assign permissions on items to roles 3. Create editor and reviewer users 4. Map users with roles 5. Create individual/group reviewer items in content tree 6. Create Custom comment window 7. Apply validation rules on comment windows field 8. Create Custom workflow 9. Apply custom workflow on items 10. Customizing workbox 11. Customizing items comments in Awaiting Approval section 12. Test all the scenarios
So let’s start let’s start and cover each step one by one.
4|Page
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE
1. Create Editor and Reviewer Roles We will create two role in Sitecore namely – Editor Group and Group and Reviewer Group Logged in into Sitecore desktop and select Security Tools -> Tools -> Role Manager
Create two new role - Editor Group, Group, Reviewer Group Assign editor role to Editor Group as Group as
5|Page
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE Assign reviewer role to Reviewer Group. Group. Technically Reviewer is one who have editor as well as publishing permissions. So instead of assigning individual roles to Reviewer group you can directly assign roles as Reviewer = Editor Group Role + Sitecore Client Publishing
6|Page
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE
2. Assign permissions on items to roles Assign permissions to these newly created role on content item. First assign permissions to Editor Group Role. Group Role. Note that we are NOT providing Delete Descendants permission to this role.
Similarly assign permissions to Reviewer Group Role. Group Role. Note that we are providing providi ng Delete Descendants as well as Administer level permission to this role.
Tips: Tips: - If you are not assigning item access permission to Roles/Users, then that user will not able to see workflow pending items in workbox.
7|Page
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE
3. Create editor and reviewer users Once we have created roles, ro les, now it’s time to create users users under these roles. Logged in into Sitecore desktop and select Security Tools -> Tools -> User Manager
Create some new users who play part of editors and reviewers. For example, we created 4 users like Editor1 Editor2 Reviewer1 Reviewer2
8|Page
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE
4. Map users with roles Assign user Editor1 and Editor2 to Editor Group. Group.
Similarly assign user Reviewer 1 and Reviewer 2 in Reviewer Group. Group.
9|Page
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE
5. Create custom multilist for showing all user who are reviewers and admin We need to show all admin admin and reviewer into multilist. However Sitecore don’t have any way to show all these user (more specifically all Reviewer). So we have to create custom multilist control and fill all the reviewers and admin. Steps to create custom multilist Select CORE Select CORE database in Sitecore desktop mode Add new item as “Custom “Custom Reviewer ” in “ /sitecore/system/Field types/List types/List Types/” Types/” as below
Fill entry “CustomReviewerList:ReviewerMultilist “CustomReviewerList:ReviewerMultilist ” in Control field as prefix:classname where CustomReviewerList CustomReviewerList is is prefix in CustomReviewerList.config CustomReviewerList.config file file and ReviewerMultilist is is class name for Multilist in below code file. Add reference of DLL “Sitecore.Client “ Sitecore.Client ” in your Visual Studio web project Create one “CustomReviewerList.config “CustomReviewerList.config”” file in “Website\App_Config\Include “Website\App_Config\Include”” folder as folder as
> >
10 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE >
Add a class in your UI solution and copy-paste below code for creating custom multilist which get Reviewer Group role Group role and domain name domain name from config file and get all users under reviewer role and admin
using System; using System; using System.Collections; System.Collections; using System.Collections.Gen System.Collections.Generic; eric; using System.Linq; System.Linq; using Sitecore.Text; Sitecore.Text; using Sitecore.Security.Acco Sitecore.Security.Accounts; unts; using System.Web.Security; System.Web.Security; using Sitecore.Configuration Sitecore.Configuration; ; using Sitecore.Diagnostics; Sitecore.Diagnostics; using System.Web.UI; System.Web.UI; using Sitecore.Resources; Sitecore.Resources; using Sitecore.Globalization Sitecore.Globalization; ; using Sitecore.Shell.Applica Sitecore.Shell.Applications.Cont tions.ContentEditor; entEditor; namespace SitecoreWebApp.UI SitecoreWebApp.UI { public interface IUsersField { IEnumerable< IEnumerable > GetSelectedUsers(); GetSelectedUsers(); IEnumerable< IEnumerable > GetUnselectedUsers(); GetUnselectedUsers(); string GetProviderUserKey( GetProviderUserKey(User User user); user); } /// /// Class /// Class to get admin and user from reviewer group /// public class UsersField UsersField : : IUsersField { private static readonly string string DomainParameterName DomainParameterName = Settings.GetSetting( Settings .GetSetting( "UsersField.DomainParameterName" ); private static readonly string string RoleParameterName RoleParameterName = Settings.GetSetting( Settings .GetSetting( "UsersField.RoleParameterName" ); private ListString _SelectedUsers; ListString _SelectedUsers; private ListString ListString SelectedUsers SelectedUsers { get { if (_SelectedUsers if (_SelectedUsers == null null) ) { _SelectedUsers = new ListString ListString(Value); (Value); } return _SelectedUsers; return _SelectedUsers; } }
11 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE private IEnumerable > _UsersInDomain; _UsersInDomain; private IEnumerable > UsersInDomain UsersInDomain { get { if (_UsersInDomain if (_UsersInDomain == null null) ) { _UsersInDomain = GetUsersInDomain(); } return _UsersInDomain; return _UsersInDomain; } } private IEnumerable > _Users; private IEnumerable > Users { get { if (_Users if (_Users == null null) ) { _Users = GetUsers(); } return _Users; return _Users; } } private string _Domain; string _Domain; private string string Domain Domain { get { if ( if (string string.IsNullOrEmpty(_Domain)) .IsNullOrEmpty(_Domain)) { _Domain = FieldSettings[DomainPa FieldSettings[DomainParameterName] rameterName]; ; } return _Domain; return _Domain; } } private UrlString _FieldSettings; _FieldSettings; private UrlString UrlString FieldSettings FieldSettings { get { if (_FieldSettings if (_FieldSettings == null null) ) { _FieldSettings = GetFieldSettings(); } return _FieldSettings; return _FieldSettings; } } private string string Source Source { get get; ; set set; ; } private string string Value Value { get get; ; set set; ; }
12 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE
private UsersField(string private UsersField( string source, source, string string value) value) { SetSource(source); SetValue(value); } private void void SetSource( SetSource(string string source) source) { Source = source; } private void void SetValue( SetValue(string string value) value) { Value = value; } private IEnumerable > GetUsersInDomain() GetUsersInDomain() { if (! if (!string string.IsNullOrEmpty(Domain)) .IsNullOrEmpty(Domain)) { return Users.Where(user Users.Where(user => IsUserInDomain(user, IsUserInDomain(user, Domain)); } return Users; return Users; } /// /// Get /// Get all user who are in Reviewer group or admin /// /// private static IEnumerable IEnumerable< > GetUsers() { IEnumerable > users = UserManager UserManager.GetUsers(); .GetUsers(); var filteredUser var filteredUser = new List List(); >(); if (users if (users != null null) ) { foreach ( foreach (var var tempUser tempUser in in users) users) { if (tempUser.IsAdminist (tempUser.IsAdministrator) rator) { //User is admin filteredUser.Add(tempUser); } else { if (! if (!string string.IsNullOrE .IsNullOrEmpty(RoleP mpty(RoleParameterNam arameterName)) e)) { foreach ( foreach (string string roleName roleName in RoleParameterName.Split( new char char[] [] { ',' ',' })) })) { if (tempUser.IsInRole(r (tempUser.IsInRole(roleName)) oleName)) { //User is from reviewer group filteredUser.Add(tempUser); }
13 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE } } } } return filteredUser; return filteredUser; } return new List List< (); >(); } private static bool IsUserInDomain( IsUserInDomain(User User user, user, string string domain) domain) { Assert.ArgumentNotNull(user, Assert .ArgumentNotNull(user, "user" "user"); ); Assert.ArgumentNotNullOrEmpty(domain, Assert .ArgumentNotNullOrEmpty(domain, "domain" "domain"); ); string userNameLowerCase = user.Profile.UserName.ToLower(); string string domainLowerCase string domainLowerCase = domain.ToLower(); return userNameLowerCase.Sta userNameLowerCase.StartsWith(dom rtsWith(domainLowerCa ainLowerCase); se); } private UrlString GetFieldSettings() GetFieldSettings() { try { if (! if (!string string.IsNullOrEmpty(Source)) .IsNullOrEmpty(Source)) { return new UrlString UrlString(Source); (Source); } } catch ( catch (Exception Exception ex) ex) { Log.Error( Log .Error(this this.ToString(), .ToString(), ex, this this); ); } return new UrlString UrlString(); (); } public IEnumerable IEnumerable< > GetSelectedUsers() { IList< IList > selectedUsers = new List List< (); >(); foreach (string foreach ( string providerUserKey providerUserKey in SelectedUsers) SelectedUsers) { User selectedUser User selectedUser = UsersInDomain.Where(user => GetProviderUserKey(user) == providerUserKey).FirstOrDefault(); if (selectedUser if (selectedUser != null null) ) { selectedUsers.Add(sel selectedUsers.Add(selectedUser); ectedUser); } } return selectedUsers; selectedUsers; } public IEnumerable IEnumerable< > GetUnselectedUsers() {
14 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE IList< IList > unselectedUsers = new List List< (); >(); foreach ( foreach (User User user user in UsersInDomain) UsersInDomain) { if if (!IsUserSelected(user)) { unselectedUsers.Add(user); } } return unselectedUsers; unselectedUsers; } private bool bool IsUserSelected( IsUserSelected(User User user) user) { string providerUserKey string providerUserKey = GetProviderUserKey(user); return IsUserSelected(provide IsUserSelected(providerUserKey); rUserKey); } private bool bool IsUserSelected( IsUserSelected(string string providerUserKey) providerUserKey) { return SelectedUsers.IndexOf(providerUserKey) return SelectedUsers.IndexOf(providerUserKey) > -1; } public string string GetProviderUserKey( GetProviderUserKey(User User user) user) { Assert.ArgumentNotNull(user, Assert .ArgumentNotNull(user, "user" "user"); ); MembershipUser membershipUser = Membership.GetUser(u Membership .GetUser(user.Profile. ser.Profile.UserName); UserName); return membershipUser.Provid membershipUser.ProviderUserKey.T erUserKey.ToString(); oString(); } public static IUsersField CreateNewUsersField( CreateNewUsersField(string string source, source, string string value) value) { return new UsersField UsersField(source, (source, value); } } /// /// Render /// Render Multi List which showing all user who are in Reviewer group or admin /// public class ReviewerMultilist : MultilistEx { private IUsersField _UsersField; _UsersField; private IUsersField UsersField { get { if (_UsersField if (_UsersField == null null) ) { _UsersField _UsersField = CreateNewUsersField() CreateNewUsersField(); ; } return _UsersField; _UsersField; } }
6. Create individual/group reviewer items in content tree
Select master database. master database. Create “Reviewer “Reviewer ” Templates with two fields – Name and Name and Reviewer . Note Reviewer field is of “Custom “Custom Reviewer ” type.
Now we have to create individual and group reviewers items in content tree. For this, create folder “Content “ Content Reviewers” Reviewers” in content tree and restrict insert option for “Reviewer “Reviewer ” ” template template items only Add some individual reviewer item as
Note we are getting all user who are reviewer and admin in our custom multi list for this newly added item.
For creating Reviewer Group, select more than one reviewer from multilist as
19 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE
We created 2 individual reviewer and one group reviewer in this step.
20 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE
7. Create Custom comment window When editor submit some item changes to reviewer, at that moment we need to show new Comment window with comment multiline textbox and preferred Reviewer dropdown. So for creating new comment window, we need to add new item “Reviewer “ Reviewer Comment Template” Template” in “/sitecore/templates/System/Workflow ” folder as
As shown in above image, we have standard Comments multiline textbox and newly added field “Preferred “Preferred Reviewer” Reviewer” which is of droplist type droplist type which pointed to all reviewer from content tree folder “Content “ Content Reviewers”. Reviewers”.
21 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE
8. Apply validation rules on comment windows field We need to warn editor if he is not selecting any reviewer while submitting it em for review. For For this we have to apply validation rule to our “Preferred “ Preferred Reviewer” Reviewer” field.
Select “ Select “/sitecore/templates/S /sitecore/templates/System/Workflow/Review ystem/Workflow/Reviewer er Comment Template/Comment Section/Preferred Reviewer” Reviewer” field Select “Required “Required”” option in Quick Action Bar, Validate Button and Validator Bar field as shown in below image
22 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE
9. Create Custom workflow We will create new workflow “Reviewer “ Reviewer Workflow” Workflow” for this example as below
Note instead of our new template in “Default “Default Comment Template” Template” field, we are still using Standard Comment Template as Template as new template should only use when editor submit item to Reviewer. Draft – This is first stage of all workflow. This state remain blank as below
23 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE Submit This is the main step where we need t o modify existing functionality as
Note in “Comment “Comment Template” Template” field, we now using Reviewer Comment Template 350. Also increase size of Comment Dialog Height by Height by 350. In Appearance Evaluator Type field, I want to customize the further action which I am writing in SitecoreWebApp.UI.CommandAppearanceEvaluator, SitecoreWebApp using using using using using
namespace SitecoreWebApp.UI SitecoreWebApp.UI { public class CommandAppearanceEvaluator : BasicWorkflowCommandAppearanceEvaluator { public override WorkflowCommandState EvaluateState(Sitecore.Data.Items. Item Item item, item, Sitecore.Data.Items. Item workflowCommand) workflowCommand) { if (Sitecore. if (Sitecore.Context Context.User.IsAdministrator) .User.IsAdministrator) { return WorkflowCommandState .Visible; } if (item if (item != null null) ) //the item might be null!
24 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE { var we = var we Sitecore.Context Sitecore.Context.Workflow.GetWorkflow(item).GetHistory(item).OrderByDescending(i .Workflow.GetWorkflow(item).GetHistory(item).OrderByDescending(i => i.Date).First(); if (we.CommentFields if (we.CommentFields != null null && && we.CommentFields.Count > 1) { var preferredReviewer var preferredReviewer = we.CommentFields[ "Preferred Reviewer"]; Reviewer" ]; if (! if (!string string.IsNullOrEm .IsNullOrEmpty(preferr pty(preferredReviewer) edReviewer)) ) { var reviewerFolderItem var reviewerFolderItem = Database.GetDatabase( Database .GetDatabase( "master" "master").GetItem( ).GetItem(new new ID ID( ("{E13D8303-4514-4DE7-A11B-05E326EFE01D}" )); var reviewerItem var reviewerItem = reviewerFolderItem.Children.Where(p => p.Name.ToLower() p.Name.ToLower() == preferredReviewer.ToL preferredReviewer.ToLower()).Fi ower()).FirstOrDefault(); rstOrDefault(); if (reviewerItem if (reviewerItem != null null) ) { var reviewerList var reviewerList = (MultilistField )reviewerItem.Fields[ "Reviewer" "Reviewer"]; ]; bool canHide bool canHide = true true; ; if (reviewerList.TargetIDs if (reviewerList.TargetIDs != null null) ) { //reviewerList.List string s string s = reviewerList.Items[0]; reviewerList.Items[0]; var membershipUser var membershipUser = Membership.GetUser(Sitecore. Membership .GetUser(Sitecore. Context Context.User.Name); .User.Name); string providerUserKey string providerUserKey = membershipUser.Provide membershipUser.ProviderUserKey.T rUserKey.ToString(); oString(); foreach ( foreach (var var valServId valServId in in reviewerList.TargetIDs) { string id string id = valServId.ToString(); if (id.ToString().ToLower().Contains(providerUserKey.ToLower())) { return WorkflowCommandState .Visible; } else { canHide = false false; ; } } } if (!canHide) if (!canHide) { return WorkflowCommandState .Hidden; } } } else { return WorkflowCommandState .Hidden; } }
25 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE } // End of item!=null return base base.EvaluateState(item, .EvaluateState(item, workflowCommand); } } }
Validation For Reviewer Here I am selecting “Max “Max Result Allowed” Allowed” as “FatalError “FatalError ” and just put error messages in other fields.
Email To Reviewer We want to send mail to Reviewer with message “There “There is an item for your approval in your workbox & the item path is $itemPath$ in language $itemLanguage$ and version $itemVersion$.” $itemVersion$. ” Where $itemPath$, $itemLanguage$ and $itemVersion$ and $itemVersion$ should should be replaced by their value in code specified in “Type” field as
26 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE
Include below code for sending mail functionality using System; using System; using System.Collections; System.Collections; using using System.Collections.Generic; using System.Linq; System.Linq; using Sitecore.Data.Items; Sitecore.Data.Items; using using Sitecore.Security.Accounts; using System.Web.Security; System.Web.Security; using Sitecore.Diagnostics; Sitecore.Diagnostics; using Sitecore.Data; Sitecore.Data; using Sitecore.Data.Fields; Sitecore.Data.Fields; using Sitecore.Workflows.Si Sitecore.Workflows.Simple; mple; using System.Net.Mail; System.Net.Mail; using System.Net; using System.Net; using using Sitecore.Configuration; namespace SitecoreWebApp.UI SitecoreWebApp.UI { public class EmailAction { // Methods private string string GetText( GetText(Item Item commandItem, commandItem, string string field, field, WorkflowPipelineArgs args) { string text string text = commandItem[field]; if (text.Length if (text.Length > 0) { return this this.ReplaceV .ReplaceVariables(te ariables(text, xt, args); } return string string.Empty; .Empty; } public void void Process( Process(WorkflowPipelineArgs WorkflowPipelineArgs args) { Assert.ArgumentNotNull(args, Assert .ArgumentNotNull(args, "args" "args"); );
27 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE ProcessorItem processorItem = args.ProcessorItem; if (processorItem if (processorItem != null null) ) { Item innerItem Item innerItem = processorItem.InnerItem; processorItem.InnerItem; string fullPath string fullPath = innerItem.Paths.FullPath; innerItem.Paths.FullPath; string from = this string from this.GetText(innerItem, .GetText(innerItem, "from" "from", , args); string to string to = this this.GetText(innerItem, .GetText(innerItem, "to" "to", , args); //sitecore/system/Workflows/Reviewer // sitecore/system/Workflows/Reviewer Workflow/Draft/Submit/Email To Reviewer if (innerItem.ID.ToString().ToLower().Equals( "{83ffff97-673c-444d-aec8if c57acb871f0e}" )) //Send mail to reviewer { to = GetReviewerMail(args.CommentFields); } else if if (innerItem.ID.ToString().ToLower().Equals( "{510cd88a-9749-4fc2b15b-1a6e935a2650}" ) || innerItem.ID.ToString().ToLower().Equals( "{8D8EC9D2-C1EE-4026AD8F-BA16C73B65AB}" )) //Send mail to Editor { //sitecore/system/Workflows/Reviewer // sitecore/system/Workflows/Reviewer Workflow/Awaiting Approval/Reject/Email To Editor //sitecore/system/Workflows/Reviewer // sitecore/system/Workflows/Reviewer Workflow/Awaiting Approval/Approve/Email To Editor to = GetEditorEmail(args.DataItem); to += to.Contains(";" to.Contains( ";") ) ? "" "" : : ";" ";"; ; } if (! if (!string string.IsNullOrEmpty(to)) .IsNullOrEmpty(to)) { try { //Network Credentials string host string host = Settings Settings.GetSetting( .GetSetting( "MailServer" "MailServer"); ); var smtpClient var smtpClient = new SmtpClient SmtpClient(host); (host); smtpClient.Credentials = new NetworkCredential (Settings Settings.GetSetting( .GetSetting( "MailServerUserName" ), Settings.GetSetting( Settings .GetSetting( "MailServerPassword" )); int port; int port; smtpClient.Port = int.TryParse( int .TryParse(Settings Settings.GetSetting( .GetSetting( "MailServerPort" ), out out port) port) ? port : 25; string mailSubject = this string mailSubject this.GetText(innerItem, .GetText(innerItem, "subject" "subject", , args); string mailMessage string mailMessage = this this.GetText(innerItem, .GetText(innerItem, "message" "message", , args); Error.Assert(to.L Error .Assert(to.Length ength > 0, "The 'To' field is not specified in the mail action item: " + " + fullPath); Error.Assert(from Error .Assert(from.Length .Length > 0, "The 'From' field is not specified in the mail action item: " + fullPath); Error.Assert(mailSubject.Length Error .Assert(mailSubject.Length > 0, "The 'Subject' field is not specified in the mail action item: " + fullPath); Error.Assert(host Error .Assert(host.Length .Length > 0, "The 'Mail server' field is not specified in the mail action item: " + fullPath); foreach ( foreach (string string tempTo tempTo in in to.Split( to.Split(new new char char[] [] { ';' ';' })) }))
28 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE { if (!string if (! string.IsNullOrEmpty(tempTo)) .IsNullOrEmpty(tempTo)) { MailMessage message MailMessage message = new MailMessage MailMessage(from, (from, tempTo) { Subject = mailSubject, mailSubject, Body = mailMessage }; try { //Send mail smtpClient.Send(message); } catch ( catch (Exception Exception ex) ex) { string err string err = ex.Message; } } } } catch ( catch (Exception Exception ex) ex) { string err string err = ex.Message; } } } } private string string ReplaceVariables( ReplaceVariables(string string text, text, WorkflowPipelineArgs args) { text = text.Replace("$itemPath$" text.Replace( "$itemPath$" , args.DataItem.Paths.FullPath); text = text.Replace("$itemLanguage$" text.Replace( "$itemLanguage$" , args.DataItem.Language.ToString()); text = text.Replace("$itemVersion$" text.Replace( "$itemVersion$", , args.DataItem.Version args.DataItem.Version.ToString( .ToString()); )); return text; return text; } public string string GetEditorEmail( GetEditorEmail(Item Item item) item) { if (item if (item == null null) ) return string string.Empty; .Empty; if (item[ if (item["__Updated "__Updated by"] by" ] != null null) ) { string userName string userName = item["__Updated item[ "__Updated by"]; by" ]; User user User user = User User.FromName(userName, .FromName(userName, false false); ); if (user if (user != null null) ) { return return user.Profile.Email; } } return string string.Empty; .Empty; }
29 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE private string string GetReviewerMail(Sitecore.Collections. StringDictionary CommentFields) { string reviewers string reviewers = string string.Empty; .Empty; if (CommentFields if (CommentFields != null null && && CommentFields.Count CommentFields.Count > 1) { var preferredReviewer var preferredReviewer = CommentFields[ "Preferred Reviewer"]; Reviewer" ]; if (! if (!string string.IsNullOrEmpty(preferredReviewer)) .IsNullOrEmpty(preferredReviewer)) { //Point to folder - /sitecore/content/Global Content/Reviewers var reviewerFolderItem var reviewerFolderItem = Database Database.GetDatabase( .GetDatabase("master" "master").GetItem( ).GetItem(new new ID( ID ("{89058D11-56DB-48BC-87D3-F053B95FC7B8}" )); var reviewerItem = reviewerFolderItem.Children.Where(p => var reviewerItem p.Name.ToLower() p.Name.ToLower() == preferredReviewer.ToL preferredReviewer.ToLower()).Fir ower()).FirstOrDefault( stOrDefault(); ); if (reviewerItem if (reviewerItem != null null) ) { var reviewerList var reviewerList = (MultilistField )reviewerItem.Fields[ "Reviewer" "Reviewer"]; ]; if (reviewerList.TargetIDs if (reviewerList.TargetIDs != null null) ) { IEnumerable< IEnumerable > users = UserManager UserManager.GetUsers(); .GetUsers(); if (users != null if (users null) ) { foreach ( foreach (var var tempUser tempUser in in users) users) { if (! if (!string string.IsNullOrEmpty(tempUser.Profile.Email) .IsNullOrEmpty(tempUser.Profile.Email) && !string string.IsNullOrEm .IsNullOrEmpty(tempUs pty(tempUser.Identity er.Identity.Name)) .Name)) { var membershipUser var membershipUser = Membership.GetUser(t Membership .GetUser(tempUser.Id empUser.Identity.Name entity.Name); ); if (membershipUser != null if (membershipUser null) ) { string providerUserKey string providerUserKey = membershipUser.Provide membershipUser.ProviderUserKey.T rUserKey.ToString(); oString(); if (providerUserKey if (providerUserKey != null null) ) { foreach ( foreach (var var valServId valServId in reviewerList.TargetIDs) { if (Convert Convert.ToString(valServId).ToLower().Contains(providerUserKey.ToLower())) .ToString(valServId).ToLower().Contains(providerUserKey.ToLower())) { reviewers += tempUser.Profile.Email + ";" ";"; ; } } } } }
30 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE } } } } } } return string string.IsNullOrEmpty(reviewers) .IsNullOrEmpty(reviewers) ? string string.Empty .Empty : reviewers.Substring(0, reviewers.Length - 1); } } }
Awaiting Approval This state remain blank like draft state. Approve Reviewer takes the action on item and approving for Publish. In “ In “ Appearance Appearance Evaluator Type” Type” field, field, I want to customize the same action as mentioned in Submit Command code.
Email To Editor Send approval mail message to editor. Functionality and setting is same as described in “Email “Email To Reviewer ” section. Reject Reviewer takes the action on item and rejecting the item for some correction/improvements. correction/improvements. This step again send item for draft stage. In “ In “ Appearance Appearance Evaluator Type” Type” field, field, I want to customize the same action as mentioned in Submit Command code.
31 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE Email To Editor Send rejection mail message to editor. Functionality and setting is same as described in “Email “Email To Reviewer ” section. Approved Set workflow state to Final. This is the last state of any workflow. Auto Publish This action publish the item. If Parameters field i s set as “deep=1 “ deep=1”” which indicate publish item and its children while “deep=0 “ deep=0”” mean publish only the item. i tem.
32 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE
10. Apply custom workflow on items Now we have create roles, users, assigned user to role, created reviewer, created custom comment window and custom workflow. Now its time to apply t his workflow to content tree items. Best way of applying workflow is on template’s standard value where one need to set Default Workflow field to the name of workflow as follows
33 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE
11. Submit to Reviewer for approval Once workflow is assigned. Login as an editor and create some items and submit to reviewer for review. Editor can submit item to single reviewer. A reviewer should receive a review request mail for this submitted item.
Editor can also submit item to group reviewer. All reviewer within a group should receive a review request mail.
34 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE
If editor will not select any reviewer, a warning message should display. Editor can still submit the item which finally review only by reviewer who is admin.
35 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE
12. Customizing workbox We have to restrict reviewer to approve or reject all items at a time. So we want to disabled Approve disabled Approve All , Reject All , Approve Selected and and Reject Selected button button in workbox. However all these buttons are enabled only for Admin. Copy “Website\sitecore\shell\Ap “Website\sitecore\shell\Applications\Work plications\Workbox\workbox.x box\workbox.xml ml”” file and paste it into “Website\sitecore\shell\Override “Website\sitecore\shell\Override”” folder. Open this “Website\sitecore\shell\Override\Workbox.xml “Website\sitecore\shell\Override\Workbox.xml”” and find below line Replace this line with your Namespace.ClassName, Namespace.ClassName, AssemblyName as Include below code for disabling Approve disabling Approve All , Reject All , Approve Selected and and Reject Selected button button in workbox using Sitecore.Shell.Applica Sitecore.Shell.Applications.Workb tions.Workbox; ox; namespace SitecoreWebApp.UI SitecoreWebApp.UI { public class ReviewerWorkbox : WorkboxForm { protected override void DisplayState( DisplayState(global global::Sitecore.Workflows. ::Sitecore.Workflows. IWorkflow workflow, global global::Sitecore.Workflows. ::Sitecore.Workflows. WorkflowState state, global global::Sitecore.Data. ::Sitecore.Data.DataUri DataUri[] [] items, System.Web.UI.Control System.Web.UI. Control control, control, int int offset, offset, int int pageSize) pageSize) { base.DisplayState(workflow, base .DisplayState(workflow, state, items, control, offset, pageSize); if (!Sitecore. if (!Sitecore.Context Context.User.IsAdministrator) .User.IsAdministrator) { if (control.Controls if (control.Controls.Count .Count > 0) { var borderControls var borderControls = control.Controls[control.Controls.Count 1].Controls; for ( for (var var i i = 0; i < borderControls.Count; i++) { borderControls[i].Visible = false false; ; } } } } } }
36 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE Check below image for workbox action button are only available for Admin.
37 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE
13. Customizing items comments in Awaiting Approval section Along with comments, Reviewers wants Preferred Reviewer name Reviewer name and Item Path in comments section. Tips: Tips: - If you are unable to see pending items in workbox, assign access permission to Roles/Users as explained in step 2 Assign permissions on items to roles. roles .
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE Include below code for customizing comments in workflow using Sitecore.Pipelines.Ge Sitecore.Pipelines.GetWorkflowCo tWorkflowCommentsDispl mmentsDisplay; ay; namespace SitecoreWebApp.UI SitecoreWebApp.UI { public class CommentsFieldExtractor { public void void Process( Process(GetWorkflowCommentsDisplayArgs GetWorkflowCommentsDisplayArgs args) { if (args.ProcessorItem if (args.ProcessorItem != null null) ) { var actionsRaw var actionsRaw = args.WorkflowEvent.CommentFields[ "Preferred Reviewer"]; Reviewer" ]; if ( if (string string.IsNullOrEmpty(actionsRaw)) .IsNullOrEmpty(actionsRaw)) { return; return ; } //Add item path in existing comment args.Comment += " Item Path: " + " + args.Item.Paths.FullPath; } } } }
39 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE
14. How to apply workflow on existing multiple items i tems Use below program to apply workflow on all the children of particular item. Here is a code to do it. Just put Home item ID or start item ID var FirstItem = Sitecore.Context var FirstItem Sitecore. Context.Database.GetItem( .Database.GetItem( new ID ID( ("{A00A11B6-E6DB-45AB-8B54636FEC3B5523}" )) ; ApplyWorkflow(FirstIte ApplyWorkflow(FirstItem m , 1);
Rest of the item should be iterate by following recursive function and apply workflow on all the items except folder item. private Item ApplyWorkflow( ApplyWorkflow(Item Item mainItem, mainItem, int int icounter) icounter) { try { if if (!mainItem.DisplayName.Equals( "Homepage" "Homepage")) )) { //Put Workflow ID here var workflow var workflow = Factory.GetDatabase( Factory .GetDatabase("master" "master").WorkflowProvider.GetWorkflow( ).WorkflowProvider.GetWorkflow( "{7E9BC450-18EE-401E-892A1CEF27BF8D9B}" ); workflow.Start(mainItem); } } catch { } if (mainItem.HasChildren) (mainItem.HasChildren) { icounter++; foreach ( foreach (var var myInnerItem myInnerItem in mainItem.Children.ToLi mainItem.Children.ToList()) st()) { ApplyWorkflow(myInnerI ApplyWorkflow(myInnerItem, tem, icounter); } } icounter--; return null null; ; }
40 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE
How to test all the scenarios
We have to test our workflow and note the finding. For this we need to create different scenarios from testing point of view. Create some editors, reviewers and group reviewers as Editors: Editor 1, Editor 2, Editor 3 Reviewer: Reviewer 1, Reviewer 2 Reviewer Group: Reviewer G1 (2 G1 (2 nos - G1 R1, R1, G1 R2) R2) Reviewer G2 (2 G2 (2 nos - G2 R1, R1, G2 R2) R2)
Scenario 1: Editor 1: Editor edits any item and submits to selected reviewer User Action: Editor 1 1 - Item 1 version 1 submits for approval to Reviewer 2 Editor 1 1 - Item 3 version 1 submits - Reviewer G1 Expected Outcome: 1. Workbox of those reviewer (group) will show the link to approve that version of item to whom it is assigned to. 2. Email notification triggered to the reviewer/ Reviewer group on item submit 3. Email notification triggered to the editor once item is approved and that approved version of item is published and live
Scenario 2: Editor edits any item and submits for approval without any reviewer selection User Action: Editor 1 Item 1 Item 2 version 1 submits - no one Expected Outcome: 1. Only users having Admin access will be able to approve this version of item and no one else. 2. Email notification triggered to the editor once i tem is approved and that approved version of item is published and live
41 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE Scenario 3: Same item being edited by multiple editors and submitted for approval by each editor User Action: Editor 2 2 - Item 1 Version 2 submit - Reviewer 1 Editor 3 Item 3 Item 1 Version 3 submit - Reviewer G2 Reviewer 1 Item 1 Item 1 edit and approve Expected Outcome: 1. Workbox of those reviewer (group) will show the link to approve those versions of item to whom it is assigned to. 2. Email notification triggered to the reviewer/ Reviewer group on item submit 3. Email notification triggered to the editor once item is approved and that approved version of item is published and live 4. Only the latest approved version of item is published and will live.
Scenario 4: Item submitted to approver Group and multiple approver in Group try to open/approve that item User Action: Reviewer G1 G1 - Item 3 - opened by multiple reviewer of G1. G1. G1 R1 R1 - Opens, edit then approve G1 R2 R2 - Opens, approve
Expected Outcome: 1. Workbox of those reviewer (group) will show the li nk to approve those versions of item to whom it is assigned to. 2. Email notification triggered to the reviewer/ Reviewer group on item submit 3. Email notification triggered to the editor once item is approved and that approved version of item is published and live 4. The Approval link/ button can be submitted once by the reviewer who clicked that first. On refresh the workbox of the other user will not display the item event if he is about to submit that.
42 | P a g e
ALL ABOUT CUSTOMIZING WORKFLOW IN SITECORE Scenario 5: Item send for approval by a reviewer is rejected. User Action: Editor 2 2 - Item 2 version 2 send for approval to Reviewer 1. 1. Reviewer 1 Rejects Expected Outcome: 1. Workbox of those reviewer (group) will show the link to approve that version of item to whom it is assigned to. 2. Email notification triggered to the reviewer/ Reviewer group on item submit 3. Email notification triggered to the editor once item is rejected by the approver.
Scenario 6: Item submitted to approver Group and multiple approver in Group try to do different action for that item User Action: Item 3 version 2 submitted for approval by Editor 2 to 2 to Reviewer G 1 G1 R1 R1 - Opens, edit then approve G1 R2 R2 - Rejects Expected Outcome: 1. Workbox of those reviewer (group) will show the link to approve those versions of item to whom it is assigned to. 2. Email notification triggered to the reviewer/ Reviewer group on item submit. 3. Approve or Rejection by the approver depends on who clicks the li nk first and other approvers browsers will be refreshed on page refresh. Email notification triggered to the editor once i tem is approved/Rejected and that approved version of item is published and live.