9

I am using Sitecore 9.1.1.

I need to know if I can set up a workflow that activates when a Sitecore item is "Deleted" from the content editor, so that the item does not actually get deleted until the workflow gets approved.

Is there a way to interrupt the delete event that is triggered on deleting items from the content editor? If yes, how can I add a "Delete" action that triggers after the approval of the workflow?

6

One way you could handle this would be to remove access (through security) to Delete items in the tree and instead add a workflow command that sends the items through a "Delete Workflow."

1. Restrict delete access

Remove the Delete access right from wherever you have defined it in your content tree.

Note: Unless you want everyone to be subject to this new workflow for deletion, I would not apply the Deny access right. Instead, remove the Grant access right. Applying Deny to a low-level role will cause others to lose this access, as well (since Deny always beats Grant).

2. Create custom deletion workflow action

In the workflow action which deletes the item (in the example above, Delete Workflow/Pending Deletion Approval/Approve for Deletion/Delete), you will need to specify a type that contains a method to actually perform the deletion.

using System.Collections.Specialized;
using Sitecore.Data.Items;
using Sitecore.SecurityModel;
using Sitecore.Web;
using Sitecore.Workflows.Simple;

public class Delete
{
    public void Process(WorkflowPipelineArgs args)
    {
        Item dataItem = args.DataItem;
        Item workflowActionItem = args.ProcessorItem.InnerItem;
        NameValueCollection nameValueCollection = WebUtil.ParseUrlParameters(workflowActionItem["parameters"]);

        using (new SecurityDisabler())
        {
            dataItem.Recycle();
        }
    }
}

3. Add delete workflow states and commands to your workflow

Add the steps you want for handling deletion. A few things to consider:

  1. The Delete workflow command should be in the Draft state since you can't engage with it from the final workflow state (users will have to Lock and Edit to delete)
  2. There should be a step to get out of the deletion workflow (e.g. Reject Deletion below)
  3. There needs to be a workflow action which references your custom action above to actually perform the deletion
  4. Don't forget to secure the workflow states and actions according to your security roles

Example

Workflow item hierarchy

  • This is an interesting approach, thank you for your answer! I will add that I mixed your answer with the use of the "uiDeleteItems" processor rather than put a Delete option in the Draft state, which also worked for me – Mie ElMansy Jun 12 '19 at 9:27
  • If you can, I would suggest adding an answer with your final solution to help others with the same issue. – Dan Sinclair Jun 12 '19 at 11:58
  • I will in a bit ^^ – Mie ElMansy Jun 12 '19 at 12:04
3

Building on some of the information Dan Sinclair's answer provided, I reached the following solution:

I needed a way to capture the "Delete" event, but only when it was triggered from inside the content editor, so after some research I found the "uiDeleteItems" processor in the Sitecore.config file, which does exactly what I want.

<uiDeleteItems>
  <processor mode="on" type="Sitecore.Shell.Framework.Pipelines.DeleteItems,Sitecore.Kernel" method="CheckPermissions" />
  <processor mode="on" type="Sitecore.Shell.Framework.Pipelines.DeleteItems,Sitecore.Kernel" method="Confirm" />
  <processor mode="on" type="Sitecore.Shell.Framework.Pipelines.DeleteItems,Sitecore.Kernel" method="CheckTemplateLinks" />
  <processor mode="on" type="Sitecore.Shell.Framework.Pipelines.DeleteItems,Sitecore.Kernel" method="CheckCloneLinks" />
  <processor mode="on" type="Sitecore.Shell.Framework.Pipelines.DeleteItems,Sitecore.Kernel" method="CheckLinks" />
  <processor mode="on" type="Sitecore.Shell.Framework.Pipelines.DeleteItems,Sitecore.Kernel" method="CheckLanguage" />
  <processor mode="on" type="Sitecore.Shell.Framework.Pipelines.DeleteItems,Sitecore.Kernel" method="UncloneItems" />
  <processor mode="on" type="Sitecore.Shell.Framework.Pipelines.DeleteItems,Sitecore.Kernel" method="Execute" />
  <processor mode="on" type="Sitecore.Shell.Framework.Pipelines.DeleteItems,Sitecore.Kernel" method="PostAction" />
</uiDeleteItems>

To use this processor, I added a config patch that looked like this, the [2] is to ensure it triggers after the "Confirm" is clicked in the default delete item popup check in Sitecore.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <processors>
      <uiDeleteItems>
        <processor mode="on" type="YourAbortDeleteFunctionClass, YourNamespace" method="AbortDelete" patch:after="processor[@type='Sitecore.Shell.Framework.Pipelines.DeleteItems,Sitecore.Kernel'][2]"/>
      </uiDeleteItems> 
    </processors>
  </sitecore>
</configuration>

In my workflow, I added 2 states: "(awaiting) Deletion Approval" and "Deletion Approved"

enter image description here

and I made it so that, on clicking "Delete" from the content editor, I would "interrupt" that process and set my current item's workflow state to "Deletion Approval" instead, making it enter my workflow cycle.

Afterwards, if this item's deletion approval is accepted, it would move to the "Deletion Approved" state, and the item would get recycled/deleted in my custom code for "Delete Item" action.

Next, I added my AbortDelete() function, which would set the state of my item to "Awaiting deletion" and not delete it, and it looked something like this:

public void AbortDelete(ClientPipelineArgs args)
{

    using (new Sitecore.SecurityModel.SecurityDisabler())
    {
        try
        {
            item.Editing.BeginEdit();
            item.Fields["__Workflow"].Value = YourWorkflowID;
            item.Fields["__Workflow State"].Value = YourDeletionStateID;
            item.Editing.EndEdit(true, false);
        }
        catch (Exception ex)
        {
            Sitecore.Diagnostics.Log.SingleError("Error in workflow state set on deleteui" + ex, ex);
        }

    }
    args.AbortPipeline();

}

The last thing you need to do is write some code to actually delete the items that get "Deletion approved", and add it to your "Delete Item" action, should be something along these lines

public class DeleteApproved
{
    public void Process(WorkflowPipelineArgs args)
    {
        Assert.ArgumentNotNull(args, "args");
        ProcessorItem processorItem = args.ProcessorItem;
        Item currentItem = args.DataItem;
        if (currentItem != null)
        {
            Item innerItem = currentItem;
            innerItem.Recycle(); //send to recyclebin from master db

            var webDB = Sitecore.Configuration.Factory.GetDatabase("web");
            Item webItem = webDB.GetItem(currentItem.ID);
            webItem.Recycle(); //send to recyclebin from web db
        }
    }
}

enter image description here

Hope this helps!

  • in AbortDelete, where is item defined? Does your class inherit anything? – Erica Stockwell-Alpert Nov 13 '19 at 16:14
  • @EricaStockwell-Alpert I accessed the items from the args input parameter, like this ListString items = new ListString(args.Parameters["items"], '|');, then i looped over the "items" list and accessed each "item" like this: Item item = masterDB.GetItem(new ID(items[i])); – Mie ElMansy Nov 14 '19 at 9:57
0

Generally, Sitecore Workflow engine does not provide the "__OnDelete" action out of the box.

Only the "__OnSave" action is supported. This action is defined in the following pipeline:

Sitecore.Pipelines.Save.WorkflowSaveCommand

enter image description here

The main Process method of the WorkflowSaveCommand contains the following code:

Item obj1 = Client.ContentDatabase.GetItem(saveItem.ID, saveItem.Language, saveItem.Version);
    if (obj1 != null)
    {
      IWorkflow workflow = workflowProvider.GetWorkflow(obj1);
      if (workflow != null && workflow.GetState(obj1) != null)
      {
        foreach (WorkflowCommand command in workflow.GetCommands(obj1))
        {
          Item obj2 = Client.ContentDatabase.GetItem(command.CommandID);
          if (obj2 != null && !(obj2.Name != "__OnSave"))
            workflow.Execute(command.CommandID, obj1, string.Empty, false, Array.Empty<object>());
        }
      }
    }

According to this, you need:

  1. Implement a custom "__OnDelete" item event handler subscribed the "item:deleted" event;
  2. Implement a pretty the same logic as above using "__OnDelete" keyword where it needs;
  3. Create "__OnDelete" action in your workflow (just take a look at the Default Sample workflow, as an example):

enter image description here

  • hey, thanks for your answer! I needed something that didn't trigger on "item:deleted" in general, but rather only when the "Delete" happens from the content editor, so I ended up using the "uiDeletedItem" processor instead. – Mie ElMansy Jun 12 '19 at 14:59
  • hey @mie, no problem. that is really interesting question. glad you implement your solution as well – Vadzim Papko Jun 12 '19 at 16:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.