Automatic unlock items

There has been many posts about unlocking Sitecore items, and here’s yet another one that perhaps fits your needs.

In one of our projects, we decided that users that are not logged into Sitecore, doesn’t need to retain locks on any items, so we wanted a solution where Sitecore automatically releases all item locks where the user doesn’t have an active session.

To make the solution efficient, I decided on re-using the LockedBy computed index field I described in my earlier post about speeding up the Sitecore Experience Editor. Using that index field, it is simple and really fast to find all items that are locked by users that doesn’t have a session.

So I essentially created a scheduled agent that is executed every hour, or as often as needed.

First we need the LockedBy Index field and a class that represents a locked item in the index, like this:

public class IsLockedByComputedField : AbstractComputedIndexField
{
	public override object ComputeFieldValue(IIndexable indexable)
	{
		Item obj = indexable as SitecoreIndexableItem;
		if (obj == null)
			return null;
		if (!obj.Locking.IsLocked())
			return null;
		return obj.Locking.GetOwner();
	}
}

public class LockedItem
{
	[IndexField("_group")]
	public Guid ID { get; set; }

	[IndexField("_language")]
	public string LanguageName { get; set; }

	[IndexField("_version")]
	public int Version { get; set; }

	[IndexField("lockedby")]
	public string LockedBy { get; set; }
}

Then we need an agent that is executed periodically. When executed, I load all users that are having locked items using the index:

var searchContext = ContentSearchManager.GetIndex("sitecore_master_index").CreateSearchContext();
var usersHavingLock = searchContext.GetQueryable<LockedItem>()
	.FacetOn(f => f.LockedBy)
	.GetFacets()
	.Categories.FirstOrDefault();

Now we have a facet list with all users. Now we can get a list of all user sessions:

var loggedInUsers = new HashSet<string>();
using (new SecurityDisabler())
{
	foreach (var session in DomainAccessGuard.Sessions)
	{
		if (!loggedInUsers.Contains(session.UserName))
			loggedInUsers.Add(session.UserName);
	}
}

With these two lists, we can simply find all users that have items that should be unlocked:

var usersToUnlock = new List<string>();
foreach (var userFacet in usersHavingLock.Values.Where(f => f.AggregateCount > 0))
{
	if (!loggedInUsers.Contains(userFacet.Name)) 
		usersToUnlock.Add(userFacet.Name);
}

Now we can simply look through all users and find all items to unlock them:

var db = Factory.GetDatabase("master");
foreach (var userToUnlock in usersToUnlock)
{
	// Find all items that is locked by user
	Sitecore.Diagnostics.Log.Info("Unlocking items owned by "+ userToUnlock, this);
	var username = userToUnlock;
	var itemsToUnlock = searchContext.GetQueryable<LockedItem>()
		.Where(i => i.LockedBy == username)
		.GetResults()
		.Hits.Select(i => i.Document);

	// Unlock each item
	foreach (var lockedItem in itemsToUnlock)
	{
		var id = new ID(lockedItem.ID);
		var language = LanguageManager.GetLanguage(lockedItem.LanguageName);
		var version = Sitecore.Data.Version.Parse(lockedItem.Version);
		var item = db.GetItem(id, language, version);
		if (item != null)
		{
			Sitecore.Diagnostics.Log.Info("Unlocking item" + item.Uri.ToString(ItemUriFormat.Uri), this);
			using (new EditContext(item, SecurityCheck.Disable))
			{
				item.Locking.Unlock();
			}
		}
	}
}