Archive for November, 2014

Enforce alt text for image fields in Sitecore

Alt text in images have their unique place with regard to accessibility and search engine optimization. Seeing as search engines cannot read images or any possible text in them, the only way for the crawlers to find ‘relevant’ images using alt text set on them.

Sitecore by default, does mark image fields which point to images without alt texts set on them with a red flag, but that being said, the content author can choose to just ignore this red flag and move forward and save / publish the item.

Which is why it made perfect sense when a client asked if we could implement a way by which a user would be prevented from saving an item if it used an image with no alt text set on it.

We created a custom field rule and associated this with the image type – this meant that this rule applied to all images in the site. You could also choose to apply the validation rule on individual fields on templates.

The custom validation code, basically used the same code that the default alt validation code which sitecore comes with uses, with the exception of the severity of the error. In our code we set it to a higher level, that would not just warn the content author – but would also prevent them from saving the item at all.

In sitecore:

2014-11-04_042939

There are no config changes involved here. You only need to ensure that the fully qualified class name and the assembly name are included as above.

Here is where we set this validator to all image fields:
2014-11-04_043037

Here’s the code used in the custom validator:

using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.Data.Validators;
using System;
using System.Runtime.Serialization;
using Sitecore.Diagnostics;
using Sitecore.Shell.Applications.ContentEditor;

namespace *****************._Classes.*************.Customizations.Customized_Sitecore
{
    [Serializable]
    public class ImageAltValidator : StandardValidator
    {
        public ImageAltValidator(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {

        }

        public ImageAltValidator()
        {

        }

        public override string Name
        {
            get { return "ImageAltValidator"; }
        }

        protected override ValidatorResult Evaluate()
        {
            ItemUri itemUri = this.ItemUri;

            if (itemUri == (ItemUri) null)
                return ValidatorResult.Valid;

            Field field = this.GetField();
            if (field == null)
                return ValidatorResult.Valid;

            string str1 = field.Value;
            if (string.IsNullOrEmpty(str1) || string.Compare(str1, "<image />", StringComparison.InvariantCulture) == 0)
                return ValidatorResult.Valid;
            XmlValue xmlValue = new XmlValue(str1, "image");
            string attribute = xmlValue.GetAttribute("mediaid");
            string str2;
            if (!str1.StartsWith("/"))
                str2 = string.Empty;
            else
                str2 = "/sitecore/media library/" + str1.TrimStart(new char[1]
                {
                    '/'
                });

            string path = str2;

            if (string.IsNullOrEmpty(attribute) && string.IsNullOrEmpty(path) || !string.IsNullOrEmpty(xmlValue.GetAttribute("alt")))
                return ValidatorResult.Valid;

            Database database = Factory.GetDatabase(itemUri.DatabaseName);
            Assert.IsNotNull((object) database, itemUri.DatabaseName);
            MediaItem mediaItem = (MediaItem) (!string.IsNullOrEmpty(attribute) ? database.GetItem(attribute, itemUri.Language) : database.GetItem(path, itemUri.Language));
            if (mediaItem == null || !string.IsNullOrEmpty(mediaItem.Alt))
                return ValidatorResult.Valid;

            this.Text = this.GetText("Alternate text is missing in the Image field \"{0}\".", new string[1]
            {
                field.DisplayName
            });
            return this.GetFailedResult(ValidatorResult.FatalError);
        }

        protected override ValidatorResult GetMaxValidatorResult()
        {
            return base.GetFailedResult(ValidatorResult.FatalError);
        }
    }
}

Alternately, you could set this to any specific field:
2014-11-04_043709

So now, when a content author tries to save an item which has an image missing its alt text:
2014-11-04_044040

, , , , , , , , , , , ,

Leave a comment

Circular reference exception on trying to serialize glass objects

While using sitecore 7 with TDS and glassmapper, we ran into this issue where serializing glass mapper objects as they are into json, resulted in a circular reference exception.

2014-11-01_165346

You will see that the T4 template file glassv3header.tt file has the Language property available as an object of the Sitecore.Globalization.Language class:

namespace <#=fullNamespace #>
{
	public partial interface IGlassBase{
		
		[SitecoreId]
		Guid Id{ get; }

		[SitecoreInfo(SitecoreInfoType.Name)]
		string Name { get; }

		[SitecoreInfo(SitecoreInfoType.Language)]
		Sitecore.Globalization.Language Language{ get; }

        [SitecoreInfo(SitecoreInfoType.Version)]
        int Version { get; }

		[SitecoreInfo(SitecoreInfoType.Url)]
		string Url { get; }
	}

	public abstract partial class GlassBase : IGlassBase{
		
		[SitecoreId]
		public virtual Guid Id{ get; private set;}

		[SitecoreInfo(SitecoreInfoType.Name)]
        public string Name { get; private set; }

		[SitecoreInfo(SitecoreInfoType.Language)]
		public virtual Sitecore.Globalization.Language Language{ get; private set; }

        [SitecoreInfo(SitecoreInfoType.Version)]
        public virtual int Version { get; private set; }

		[SitecoreInfo(SitecoreInfoType.Url)]
        public virtual string Url { get; private set; }
	}
}

You could ofcourse remove this property from your tt file, but the property does prove handy, so you might want to use it!
And the CultureInfo class has within itself a bunch of CultureInfo properties which leads to the circular reference.
2014-11-03_173449

I am using the Controller.Json method for serializing the object. You could definitely go for using other frameworks like newtonsoft etc, which would give you the option of limiting the depth in case recursion is encountered while serializing.
However, if you want to continue using the inherent Controller.Json and use the JsonResult type, the best route would be to serialize an on-the-fly created anonymous object instead:

            return Json(new
            {
                InspirationsList = inspirationListingModel.InspirationsList != null ? inspirationListingModel.InspirationsList.Select(x => new
                {
                    x.Listing_Image,
                    x.Url,
                    VideoUrl = (x.Custom_Video != null && !string.IsNullOrWhiteSpace(x.Custom_Video.Url)) ? x.Custom_Video.Url : string.Empty,
                    PrimaryInspirationtype = (x.Custom_InspirationTypes != null && x.Custom_InspirationTypes.Any())
                        ? x.Custom_InspirationTypes.First().Navigation_Title : string.Empty,
                    x.Navigation_Title,
                    SkillLevel = (x.Custom_SkillLevel != null ? x.Custom_SkillLevel.Title : string.Empty)
                }) : null,
                CTACallout = inspirationListingModel.CTACallout == null ? null : new
                {
                    inspirationListingModel.CTACallout.Title,
                    inspirationListingModel.CTACallout.Subtitle,
                    inspirationListingModel.CTACallout.Callout_Image,
                    inspirationListingModel.CTACallout.Custom_Theme,
                    inspirationListingModel.CTACallout.Call_To_Action
                },
                inspirationListingModel.IsMoreAvailable,
                inspirationListingModel.TotalInspirationsCount
            }, JsonRequestBehavior.AllowGet);

In the above example InspirationsList is a collection of glass objects while CTACallout is a single glass object.

This returns json similar to the following:

{
    "InspirationsList":[
        {
            "Listing_Image":{
                "Alt":"",
                "Border":"",
                "Class":"",
                "Height":262,
                "HSpace":0,
                "Src":"/en/~/media/***********************/28.jpg",
                "VSpace":0,
                "Width":314,
                "MediaId":"8a658e45-e172-4592-95bf-*****************",
                "Title":""
            },
            "Url":"/en/********************/2014/10/25/04/19/aenean-vulputate-eleifend",
            "VideoUrl":"",
            "PrimaryInspirationtype":"Outdoor DIY",
            "Navigation_Title":"Aenean vulputate eleifend",
            "SkillLevel":"Advanced"
        },
        {
            "Listing_Image":{
                "Alt":"",
                "Border":"",
                "Class":"",
                "Height":262,
                "HSpace":0,
                "Src":"/en/~/**************************.jpg",
                "VSpace":0,
                "Width":314,
                "MediaId":"8a658e45-e172-4592-95bf-*************",
                "Title":""
            },
            "Url":"/en/*************************************",
            "VideoUrl":"",
            "PrimaryInspirationtype":"Home Projects",
            "Navigation_Title":"Maecenas nec odio et",
            "SkillLevel":"Easy"
        }
    ],
    "CTACallout":{
        "Title":"Submit",
        "Subtitle":"Your \u003cbr\u003eDIY Project",
        "Callout_Image":{
            "Alt":"CTA",
            "Border":"",
            "Class":"",
            "Height":270,
            "HSpace":0,
            "Src":"/en/~/media/*************.jpg",
            "VSpace":0,
            "Width":313,
            "MediaId":"1f3a48b0-a5d4-4e19-b73b****************",
            "Title":""
        },
        "Custom_Theme":null,
        "Call_To_Action":{
            "Anchor":"",
            "Class":"",
            "Text":"Sample Text",
            "Query":"",
            "Title":"",
            "Url":"/en/******************",
            "Target":"",
            "TargetId":"20265cce-b2ff-472c-aa85-******************",
            "Type":4
        }
    },
    "IsMoreAvailable":true,
    "TotalInspirationsCount":2
}

Also, while we are at this, to be able to access a controller action directly in a sitecore mvc solution, you would use a Url similar to the following:

http://mydomain.com/api/sitecore/<controller>/<action>?<param1=value>&<param2=value>

, , , , , , , , , , ,

Leave a comment