Custom Workflow Comments CWC Module for Sitecore 7 Part 2

Posted 11/17/2013 by asura

Part 2 of this blog post will focus on the UI aspects. We will be modifying the display of Comments in the Workbox and the History in the Review tab.

Using a refactor tool like the .NET Reflector or JetBrains dotPeek to decompile Sitecore.Client and grab code for Sitecore.Shell.Applications.Workbox.WorkboxHistoryXmlControl.

Step 1 – Override

To implement this we need to override the functionality in WorkboxHistory.xml. To do this, create the following folders under WebsitesitecoreshellOverride folder:

  • Applications

Copy WorkboxHistory.xml from WebsitesitecoreshellApplicationsWorkbox

Step 2 – Code Behind

Use a refactor tool like the .NET Reflector or JetBrains dotPeek to decompile Sitecore.Client and grab code for Sitecore.Shell.Applications.Workbox.WorkBoxHistoryXmlControl.

Add a WorkboxHistoryXmlControl class in your BL or Sitecore Extensions project to mirror the Sitecore.Shell.Applications.Workbox.WorkboxHistoryXmlControl class.

Step 3 – CodeBeside

Modify the WorkboxHistory.xml file in the override folder to replace the value in the CodeBeside tag.


Step 4 – Config

Since we are modifying an Xml Control, we need at add our library to the uiusings and uireferences nodes in the web.config.

Step 5 – Code

In your WorkboxHistoryXmlControl class, modify the OnLoad method to detect custom template comments. See the highlighted rows below:

protected override void OnLoad(EventArgs e)
            if (!Sitecore.Context.ClientPage.IsEvent)
                IWorkflowProvider workflowProvider = Sitecore.Context.ContentDatabase.WorkflowProvider;
                if (workflowProvider != null)
                    IWorkflow workflow = workflowProvider.GetWorkflow(this.WorkflowID);
                    Error.Assert(workflow != null, string.Concat("Workflow "", this.WorkflowID, "" not found."));
                    Item item = Sitecore.Context.ContentDatabase.Items[this.ItemID, Sitecore.Globalization.Language.Parse(this.Language), Sitecore.Data.Version.Parse(this.Version)];
                    if (item != null)
                        NameValueCollection nameValueCollection = new NameValueCollection();
                        NameValueCollection nameValueCollection1 = new NameValueCollection();
                        WorkflowState[] states = workflow.GetStates();
                        for (int i = 0; i < (int)states.Length; i++)
                            WorkflowState workflowState = states[i];
                            nameValueCollection.Add(workflowState.StateID, workflowState.DisplayName);
                            nameValueCollection1.Add(workflowState.StateID, workflowState.Icon);
                        WorkflowEvent[] history = workflow.GetHistory(item);
                        string name = Sitecore.Context.Domain.Name;
                        WorkflowEvent[] workflowEventArray = history;
                        for (int j = 0; j < (int)workflowEventArray.Length; j++)
                            WorkflowEvent workflowEvent = workflowEventArray[j];
                            string user = workflowEvent.User;
                            if (user.StartsWith(string.Concat(name, ""), StringComparison.OrdinalIgnoreCase))
                                user = StringUtil.Mid(user, name.Length + 1);
                            string[] strArrays = new string[] { user, Translate.Text("Unknown") };
                            user = StringUtil.GetString(strArrays);
                            string str = nameValueCollection1[workflowEvent.NewState];
                            string[] item1 = new string[] { nameValueCollection[workflowEvent.OldState], "?" };
                            string str1 = StringUtil.GetString(item1);
                            string[] strArrays1 = new string[] { nameValueCollection[workflowEvent.NewState], "?" };
                            string str2 = StringUtil.GetString(strArrays1);
                            string str3 = DateUtil.FormatDateTime(workflowEvent.Date, "D", Sitecore.Context.User.Profile.Culture);
                            XmlControl webControl = Resource.GetWebControl("WorkboxHistoryEntry") as XmlControl;
                            webControl["User"] = user;
                            webControl["Icon"] = str;
                            webControl["Date"] = str3;
                            webControl["Action"] = string.Format(Translate.Text("Changed from {0} to {1}."), str1, str2);

                            //check if the value in the Text field is an ID
                            Sitecore.Data.ID customWorkFlowItemID;
                            if (Sitecore.Data.ID.TryParse(workflowEvent.Text, out customWorkFlowItemID))
                                //get the rendered text from the fields of the custom template item from the item bucket
                                webControl["Text"] = GetWorkflowItemDetails(customWorkFlowItemID);                                
                                webControl["Text"] = workflowEvent.Text;

Also we added the GetWorkflowItemDetails method to gather all fields and their values to be rendered on the UI.

        /// Returns the output with all the fields and their values associated with the custom template item from the item bucket

/// The ID of the custom template item. private string GetWorkflowItemDetails(Sitecore.Data.ID customWorkFlowItemID) { StringBuilder returnString = new StringBuilder(); //get the item and make sure its not null Item customWorkflowItem = Sitecore.Context.ContentDatabase.GetItem(customWorkFlowItemID); if (customWorkflowItem != null) { //loop through all the item fields foreach (Field field in customWorkflowItem.Fields) { //only extract the template fields and their values if (!(field.Name == “Additional Parameters”) && (!(field.Name == “Personalization”) || UserOptions.View.ShowPersonalizationSection) && ((!(field.Name == “Tests”) || UserOptions.View.ShowTestLabSection) && RenderingItem.IsAvalableNotBlobNotSystemField(field))) returnString.AppendLine(string.Format(“{0}: {1}
“, field.DisplayName, field.Value)); } } return returnString.ToString(); }

Compile the dll and shown below are the results. 

Step 6 – Magic Code

The class to take over the WorkboxForm does all the heavy lifting. It has the logic to switch between standard functionality and Custom template functionality. The Comments method does the work for us in deciding if a custom template form needs to be displayed and how the results are going to be stored. See the highlighted rows below:

        /// Comments the specified args.

/// /// The arguments. /// public void Comment(ClientPipelineArgs args) { Assert.ArgumentNotNull(args, “args”); if (!args.IsPostBack) { bool showCustom = false; //get workflow item Item workflowItem = Context.ContentDatabase.GetItem(Context.ClientPage.ServerProperties[“workflowid”] as string); Template customTemplate = null; //check if the custom template field has a value if (workflowItem != null && !string.IsNullOrEmpty(workflowItem[new ID(Consts.FieldIDs.CustomTemplate)])) { //load custom template based on the field value customTemplate = TemplateManager.GetTemplate(new ID(workflowItem[new ID(Consts.FieldIDs.CustomTemplate)].ToString()), Context.ContentDatabase); //check if it inherits Custom Workflow Comments Base template if (customTemplate != null && customTemplate.InheritsFrom(new ID(Consts.TemplateIDs.CustomWorkflowCommentsBase))) showCustom = true; } //decide if we show the standard comments dialog or custom dialog if (showCustom) { if (!new RenderCustomTemplate() { Args = args, ItemID = new ID(Context.ClientPage.ServerProperties[“id”].ToString()), CustomTemplateID = customTemplate.ID, Language = Language.Parse(Context.ClientPage.ServerProperties[“language”] as string), Version = Sitecore.Data.Version.Parse(Context.ClientPage.ServerProperties[“version”] as string) }.Show()) return; } else { //display regular comments dialog Context.ClientPage.ClientResponse.Input(“Enter a comment:”, string.Empty); args.WaitForPostBack(); } return; } if (args.Result.Length > 2000) { Context.ClientPage.ClientResponse.ShowError(new Exception(string.Format(“The comment is too long.nnYou have entered {0} characters.nA comment cannot contain more than 2000 characters.”, args.Result.Length))); Context.ClientPage.ClientResponse.Input(“Enter a comment:”, string.Empty); args.WaitForPostBack(); return; } if (args.Result != null && args.Result != “null” && args.Result != “undefined”) { string language = Context.ClientPage.ServerProperties[“language”].ToString(); string version = Context.ClientPage.ServerProperties[“version”].ToString(); //check if the return is text comments or from a custom template IEnumerable resultingFields = (IEnumerable)RenderingParametersFieldEditorOptions.Parse(args.Result).Fields; string comments = “”; comments = args.Result; //if the result is from a custom template if (resultingFields.Count() > 0) { //get the item bucket var bucketItem = Context.ContentDatabase.GetItem(Consts.ItemIDs.WorkflowCommentsItemBucket); if (bucketItem != null && BucketManager.IsBucket(bucketItem)) { //get workflow item Item workflowItem = Context.ContentDatabase.GetItem(Context.ClientPage.ServerProperties[“workflowid”] as string); //check if the custom template field has a value if (workflowItem != null && !string.IsNullOrEmpty(workflowItem[new ID(Consts.FieldIDs.CustomTemplate)])) { // locate item in bucket by name using ( var searchContext = ContentSearchManager.GetIndex(bucketItem as IIndexable).CreateSearchContext()) { //ShortID.Encode(item.ID).ToLowerInvariant()) string searchCustomWorkflowCommentsItemTemplate = ShortID.Encode(Consts.TemplateIDs.CustomWorkflowCommentsItem).ToLowerInvariant(); string searchWorkflowItemID = ShortID.Encode(Context.ClientPage.ServerProperties[“id”].ToString()).ToLowerInvariant(); var result = searchContext.GetQueryable() .Where( x => x.Template.Equals(searchCustomWorkflowCommentsItemTemplate) && x.WorkflowItemID == searchWorkflowItemID && x.Language == language && x.Version == version) .FirstOrDefault(); //generate valid unique name string validItemName = ItemUtil.ProposeValidItemName(DateUtil.IsoNow.ToString()); Item parent = null; //based on the result, assign parent as the item found or the item bucket if (result != null) { parent = Context.ContentDatabase.GetItem(result.ID); } //load templates for Custom template and the Custom Workflow Comments Item TemplateItem customTemplate = Context.ContentDatabase.GetTemplate(new ID(workflowItem[new ID(Consts.FieldIDs.CustomTemplate)].ToString())); TemplateItem customWorkflowCommentsItem = Context.ContentDatabase.GetTemplate(new ID(Consts.TemplateIDs.CustomWorkflowCommentsItem)); using (new SecurityDisabler()) { if (parent == null) { //create an item based on Custom Workflow Comments Item and store the workflow item values Item newItem = bucketItem.Add(validItemName, customWorkflowCommentsItem); newItem.Editing.BeginEdit(); newItem.Fields[new ID(Consts.FieldIDs.CustomWorkflowCommentsItem.WorkflowItemID)].Value = Context.ClientPage.ServerProperties[“id”].ToString(); //workflow item id newItem.Fields[new ID(Consts.FieldIDs.CustomWorkflowCommentsItem.Language)].Value = language; //language newItem.Fields[new ID(Consts.FieldIDs.CustomWorkflowCommentsItem.Version)].Value = version; //version newItem.Editing.AcceptChanges(); parent = newItem; } //generate another valid unique name validItemName = ItemUtil.ProposeValidItemName(DateUtil.IsoNow.ToString()); //create an item based on the custom template you chose under the Custom Workflow Comments Item Item customItem = parent.Add(validItemName, customTemplate); customItem.Editing.BeginEdit(); foreach (FieldDescriptor fieldDescriptor in resultingFields) customItem.Fields[fieldDescriptor.FieldID].Value = fieldDescriptor.Value; customItem.Editing.AcceptChanges(); //set comments to the guid of the newly created item comments = customItem.ID.ToString(); } } } } } IWorkflowProvider workflowProvider = Context.ContentDatabase.WorkflowProvider; if (workflowProvider != null) { IWorkflow workflow = workflowProvider.GetWorkflow(Context.ClientPage.ServerProperties[“workflowid”] as string); if (workflow != null) { ItemRecords items = Context.ContentDatabase.Items; object item = Context.ClientPage.ServerProperties[“id”]; object empty = item; if (item == null) { empty = string.Empty; } Item item1 = items[empty.ToString(), Language.Parse(Context.ClientPage.ServerProperties[“language”] as string), Sitecore.Data.Version.Parse(Context.ClientPage.ServerProperties[“version”] as string)]; if (item1 != null) { try { workflow.Execute(Context.ClientPage.ServerProperties[“command”] as string, item1, comments, true, new object[0]); //workflow.Execute(Context.ClientPage.ServerProperties[“command”] as string, item1, args.Result, true, new object[0]); } catch (WorkflowStateMissingException workflowStateMissingException) { SheerResponse.Alert(“One or more items could not be processed because their workflow state does not specify the next step.”, new string[0]); } UrlString urlString = new UrlString(WebUtil.GetRawUrl()); urlString[“reload”] = “1”; Context.ClientPage.ClientResponse.SetLocation(urlString.ToString()); } } } } }

The code and module are published.

The module can be accessed using the url:

I have plans to tweak this module to add more features. If you have any questions or need the source, email me at Akshay dot sura at nttdata dot com.