The other day John West, CTO of Sitecore USA, published his 500th blog post — quite an epic feat if you ask me — where he built a custom field validator that checks whether external links in the Rich Text field resolve:
This got me thinking: what other types of field validators might be useful?
I pondered over this for the past couple of days, and couldn’t think of anything useful but finally did come up with an idea this morning (out of the blue I might add): how about field validators that check to see whether Items linked in General and Internal Link fields have presentation?
After searching through the library of field validators available in Sitecore — I did this to make sure I wouldn’t be wasting my time given that Sitecore offers a lot of field validators “out of the box” (these live under /sitecore/system/Settings/Validation Rules/Field Rules in the master database), so I suggest having a look through these before building a custom one — I came up with the following solution that employs the Template method design pattern:
using System; using System.Runtime.Serialization; using Sitecore.Data.Fields; using Sitecore.Data.Items; using Sitecore.Data.Validators; using Sitecore.Diagnostics; using Sitecore.Pipelines.HasPresentation; namespace Sitecore.Sandbox.Data.Validators.FieldValidators { public abstract class ReferencedItemHasPresentationValidator : StandardValidator { public override string Name { get { return Parameters["Name"]; } } public ReferencedItemHasPresentationValidator() { } public ReferencedItemHasPresentationValidator(SerializationInfo info, StreamingContext context) : base(info, context) { } protected override ValidatorResult Evaluate() { Item linkedItem = GetReferencedItem(); if (linkedItem == null || HasPresentation(linkedItem)) { return ValidatorResult.Valid; } Text = GetErrorMessage(); return GetFailedResult(ValidatorResult.Error); } protected virtual bool HasPresentation(Item item) { Assert.ArgumentNotNull(item, "item"); return HasPresentationPipeline.Run(item); } protected abstract Item GetReferencedItem(); protected virtual string GetErrorMessage() { string message = Parameters["ErrorMessage"]; if (string.IsNullOrWhiteSpace(message)) { return string.Empty; } return GetText(ExpandTokens(message), new[] { GetFieldDisplayName() }); } protected override ValidatorResult GetMaxValidatorResult() { return GetFailedResult(ValidatorResult.Error); } protected virtual string ExpandTokens(string value) { if(string.IsNullOrWhiteSpace(value)) { return value; } string valueExpanded = value; Field field = GetField(); if(field != null) { valueExpanded = valueExpanded.Replace("$fieldName", field.Name); } return valueExpanded; } } }
The above abstract class inherits from Sitecore.Data.Validators.StandardValidator in Sitecore.Kernel.dll — this is the base class which most validators in Sitecore inherit from — and checks to see if the Item referenced in the field has presentation (this check is done in the HasPresentation() method which basically delegates to the Run() method on the Sitecore.Pipelines.HasPresentation.HasPresentationPipeline class).
The referenced Item is returned by the GetReferencedItem() method which must be defined by subclasses of the above class.
Further, I’m passing in the validator’s name and error message through parameters (the error message allows for $fieldName as a token, and the ExpandTokens() method replaces this token with the name of the field being validated).
I then created a subclass of the above to return the Item referenced in an Internal Link field:
using System; using System.Runtime.Serialization; using Sitecore.Data.Fields; using Sitecore.Data.Items; namespace Sitecore.Sandbox.Data.Validators.FieldValidators { [Serializable] public class InternalLinkItemHasPresentationValidator : ReferencedItemHasPresentationValidator { public InternalLinkItemHasPresentationValidator() { } public InternalLinkItemHasPresentationValidator(SerializationInfo info, StreamingContext context) : base(info, context) { } protected override Item GetReferencedItem() { InternalLinkField internalLinkField = GetField(); if (internalLinkField == null) { return null; } return internalLinkField.TargetItem; } } }
Nothing magical is happening in the above class. The GetReferencedItem() method is casting the field to a Sitecore.Data.Fields.InternalLinkField instance, and returns the value of its TargetItem property.
Now that we have a class to handle Items referenced in Internal Link fields, we need another for General Link fields:
using System; using System.Runtime.Serialization; using Sitecore.Data.Fields; using Sitecore.Data.Items; namespace Sitecore.Sandbox.Data.Validators.FieldValidators { [Serializable] public class GeneralLinkItemHasPresentationValidator : ReferencedItemHasPresentationValidator { public GeneralLinkItemHasPresentationValidator() { } public GeneralLinkItemHasPresentationValidator(SerializationInfo info, StreamingContext context) : base(info, context) { } protected override Item GetReferencedItem() { LinkField linkField = GetField(); if (linkField == null) { return null; } return linkField.TargetItem; } } }
The GetReferencedItem() method in the GeneralLinkItemHasPresentationValidator class above does virtually the same thing as the same method in the InternalLinkItemHasPresentationValidator class. The only difference is the GetReferencedItem() method in the class above is casting the field to a Sitecore.Data.Fields.LinkField instance, and returns the value of its TargetItem property.
I then had to map the above field validator classes to Validation Rule Items — these have the /sitecore/templates/System/Validation/Validation Rule template — in Sitecore:
The Internal Link field validator:
The General Link field validator:
I then added two Internal Link fields on my Sample item template, and mapped the Internal Link field validator to them:
I also created two General Link fields on my Sample item template, and mapped the General Link field validator to them:
Once I had the validators mapped to their specific fields, I went ahead and removed presentation from one of my test items:
Before, the above item had these presentation components on it:
I then linked to my test items in my test fields. As you can see, there are errors on the “Bing” item which does not have presentation:
If you have any thoughts on this, or ideas for other field validators, please share in a comment.
Until next time, have a Sitecorelicious day! 😀
[…] Warn Content Authors of Linking to Items With No Presentation using Custom Field Validators in … […]