Recently I've got the request from one of my client to build assets management system with Kentico CMS. One of the main features of that system is workflow. There are sections of the site, those have to be approved by different approvers, which are nothing special, so I suggested them to build a workflow per section and assign appropriate approvers to it. Customer kindly rejected my suggestion, as they were planning to maintain thousands (if not hundreds of thousands) pages within the site, so there were too many section. Also sections were too small - sometimes that was single document.
The best solution in this scenario was to allow customer to setup approvers on the document level.
First thing I had to do was adding appropriate fields to the page type. In my case there were around 10 page types, but luckily, all they inherit the same base page type. Inheritance with page types is as much useful as it is with regular C# classes, so do not hesitate to use it whenever applicable.
In order to accomplish this task, navigate Page Types application, find appropriate page type and open edit scree for it. Go to Fields tab and add new field. Use following setting for your field:
Data type: text
Size: 200 or any other reasonable number in your particular case
Form control: Uni selector
Object type: cms.user
Return column name: username
Selection mode: Multiple
Feel free to configure the rest of the fields according to your needs.
There is one good reason to use object code name or GUID instead of object ID: if you're running synchronization using content staging, you don't have to implement object ID translation. You can find more details about translation here.
Add another field with the same settings, except object type - set it to cms.role.
Those two fields allow you to add users or roles, those should approve document, on the Form tab of particular document.
Next step is to authorize those users to perform an action on a document. This requires overriding of workflow security. So now we need to implement custom WorkflowManager class and override CheckStepPermissionsInternal:
[assembly: RegisterCustomManager(typeof(CustomWorkflowManager))]
public class CustomWorkflowManager : WorkflowManager
{
protected override bool CheckStepPermissionsInternal(TreeNode node, UserInfo user, WorkflowActionEnum action)
{
if (action == WorkflowActionEnum.Approve)
{
// get users allowed to approve from the document
var users = node.GetStringValue(<usersField>, "").Split(';').ToList();
approvers.AddRange(UserInfoProvider.GetUsers().Where(u => users.Contains(u.UserID.ToString())));
if (approvers.Contains(user))
{
return true;
}
// get roles allowed to approve document
var roles = document.GetStringValue(<rolesField>, "").Split(';').ToList();
foreach (string role in roles)
{
if (user.IsInRole(role, currentSiteName))
{
return true;
}
}
}
return base.CheckStepPermissionsInternal(node, user, action);
}
}
As you can see, document, user and workflow actions are available in this method and this is all what you need in order to figure out whether current user is in approvers list or not. In my code I'm reading appropriate fields of the document in order to get approving users and roles and checking whether current user is in one of those lists. Pay attention to the last line of the method: if user is not in those lists of approvers, I'm running default check, so I'm not screwing up default Kentico permission check. Be sure to add validation to this method in case you are going to reuse it - code above is not final and was implemented only in demo purposes.
To summarize this article I want to admit that we went through using Uni Selector control, mentioned foreign key translation while staging and overridden default Kentico WorkflowManager class. All this allowed us to override the way Kentico handles workflow and gave us possibility to configure approvers for workflow on document level, which enables us to reuse the same workflow with different sets of approvers.
PLease feel free to share your experience with the related topic or ask your questions if any.
Roman Hutnyk