Thursday, October 30, 2014

Vulnerability with using the Sitecore context search index

Mornings when production issues happen mean two things: no tea yet, and definitely no snacks until order is restored. So I was in a bit of a hurry today to find out what was going on when our default Sitecore content indexes seemed to have gone down. This was the exception thrown:

System.NullReferenceException: Object reference not set to an instance of an object. at Sitecore.ContentSearch.SitecoreItemCrawler.IsAncestorOf(Item item) at Sitecore.ContentSearch.SitecoreItemCrawler.IsExcludedFromIndex(SitecoreIndexableItem indexable, Boolean checkLocation) at Sitecore.ContentSearch.Pipelines.GetContextIndex.FetchIndex...

Here's a snippet that would cause the above error to be thrown:
Item contextItem = Sitecore.Context.Database.GetItem(SOME_ID);

if (contextItem != null)
{
    ISearchIndex index = ContentSearchManager.GetIndex(new SitecoreIndexableItem(contextItem));
}

That's just an example, but I would also see the error thrown in the __semantics field in my 'web' database.

So we're failing to retrieve the ISearchIndex... but why?

If you take a look at IsAncestorOf(Item item) (Sitecore 7.2+) using your favorite reflection tool, this is what you'll find:
// Sitecore.ContentSearch.SitecoreItemCrawler
protected virtual bool IsAncestorOf(Item item)
{
 bool result;
 using (new SecurityDisabler())
 {
  using (new CachesDisabler())
  {
   result = this.RootItem.Axes.IsAncestorOf(item);
  }
 }
 return result;
}
No null checks here, that can't be good.
The RootItem property -
// Sitecore.ContentSearch.SitecoreItemCrawler
public Item RootItem
{
    get
    {
        if (this.rootItem == null)
        {
            Database database = ContentSearchManager.Locator.GetInstance<IFactory>().GetDatabase(this.database);

            Assert.IsNotNull(database, "Database " + this.database + " does not exist");

            using (new SecurityDisabler())
            {
                this.rootItem = database.GetItem(this.Root);
            }
        }
        return this.rootItem;
    }
}
No null check here either. Fishy.

I had to dig a bit deeper to figure out where it all originates and what exactly is null.

I overwrote the Sitecore.ContentSearch.Pipelines.GetContextIndex.FetchIndex, Sitecore.ContentSearch pipeline processor to debug the code. It blew up trying to evaluate this:
   System.Collections.Generic.IEnumerable<ISearchIndex> enumerable =
                from searchIndex in ContentSearchManager.Indexes
                from providerCrawler in searchIndex.Crawlers
                where !providerCrawler.IsExcludedFromIndex(indexable)
                select searchIndex;
So the GetContextIndex pipeline tries to fetch all of the available crawlers, whose roots are ancestors of our indexable item (IsExcludedFromIndex). The ancestor check however fails on a null root item in any of the indexes.

So if you remember the good old "Root Item Not defined" error message,  it turns out this issue had the exact same cause - the RootItem for one of the search indexes was not defined in the 'web' database (i.e. the item was not yet published). Publishing is of course the quick fix.

While this is in the end a configuration issue, I still feel like the unpublished site should not be affecting ALL OF SEARCH (search based on GetContextIndex that is), which is why I'm going to leave my FetchIndex override in place and add some null checks for at least a more meaningful error message.

Tuesday, June 3, 2014

Custom rendering conditions for Sitecore presentation components

Conditional rendering rules in Sitecore are used to personalize specific components on a page based on visitor variables. It is fairly easy to extend the Rules Engine and create custom conditions to satisfy specific business needs and requirements. There are a lot of posts out there on how to create custom rules and conditions in Sitecore, so I'm going to try and keep this concise. There are three steps, The WHAT, The HOW, then Tying it all together. Enjoy!

1. The WHAT
The first step to creating a custom condition is defining what exactly it would do and choosing which base Sitecore condition type to inherit from. The below chart illustrates the inheritance between the base condition types in the Sitecore.Rules.Conditions namespace:



In this example, we are going to allow the user to select a specific value that represents a Sitecore item on the website UI, and personalize the content we render based on that value. Since this would be similar to the ItemIdCondition, which inherits from StringOperatorCondition, we will go with the same base.

2. The HOW
The below diagram illustrates the basic logic flow between our website (the almighty UI), our Sitecore rules engine component (the custom condition) and a custom value provider (a.k.a business domain code).

This separation of concerns allows us to contain all logic regarding the storage and retrieval of our value in its dedicated ValueProvider. Whether this value comes from a database, is stored in a cookie, or is a session variable is thus irrelevant to the condition and the evaluation of the condition. It also becomes irrelevant to the website UI, which will only delegate the responsibility of getting and setting the value to the same provider as well.

Here's an example of a condition class that uses a provider to retrieve the current context value. In an ideal scenario, instead of instantiated here, the provider implementation would be injected through a dependency injection container (pick your weapon carefully).

    public class SelectedValueCondition<T> : StringOperatorCondition<T> where T : RuleContext
    {
        private static ISomeValueProvider _someValueProvider;
        public static ISomeValueProvider MyAwesomeProviderInstance
        {
            get
            {
                if (_someValueProvider == null)
                {
                    _someValueProvider = new AwesomeProviderImplementation();
                }

                return _someValueProvider;
            }
        }

        private ID _itemid;
        public ID ItemID
        {
            get
            {
                return _itemid;
            }
            set
            {
                Assert.ArgumentNotNull(value, "item id");
                _itemid = value;
            }
        }

        public SelectedValueCondition()
        {
            _itemid = ID.Null;
        }

        public SelectedValueCondition(ID itemId)
        {
            Assert.ArgumentNotNull(itemId, "item id");
            _itemid = itemId;
        }
 
        protected override bool Execute(T ruleContext)
        {
            Assert.ArgumentNotNull(ruleContext, "ruleContext");

            // let's say our ValueModel class represents a domain entity that has a property named 'ID'
            ValueModel model = MyAwesomeProviderInstance.GetValue();

            ID modelId = !string.IsNullOrEmpty(model.ID) ? new ID(model.ID) : ID.Null;
            ID itemId = ItemID;

            return !modelId.IsNull && !itemId.IsNull && Compare(modelId.ToString(), _itemid.ToString());
        }
    }

3. Tying it all together
Adding our condition to Sitecore is straight-forward. An important thing to remember is to set the path of the items, from which a content editor should be able to choose from:

And that is it! Now this rule is ready to be applied to any presentation component that uses a data source.

Friday, May 30, 2014

Building support for multilingual content labels in Sitecore 7

When developing a multilingual site, one of the considerations (although maybe not one of the major ones) is translating all of those labels on buttons and other static elements on the page.

There are a couple of ways you could deal with those:
1.     Create a resource file to store string translations
2.     Use the built-in Sitecore dictionary: ex. Sitecore.Globalization.Translate.Text("More") 

From the two above, I would prefer to leave all translation responsibilities to the content editors (and possibly translation services...). However, the Sitecore Dictionary is hidden under /sitecore/system, which is not always available to content editors due to various permissions and access rights.


I wanted a way to allow the content editors to use a translation layer without having to alter permissions. So I created a Dictionary under /sitecore/content that would be accessible to content editors to create items and language versions. Since I didn't care much about the content hierarchy within the dictionary and I wanted quick and efficient word lookups, I marked the DictionaryItem template as bucketable and made my Dictionary a bucket.














I use the name of the dictionary item as a key for my look up and run a query to retrieve the first available search result with that name. Then all that's left is to get the corresponding item in the language version that's requested and return the text value.

This will work when you don’t want to grant your editors access to /sitecore/system. Content editors enter text into the item and create different language versions, then the static elements of the page get updated in accordance with the language version being requested. I haven’t fully tested the performance implications of doing this vs. the built-in Sitecore Dictionary, but I don’t foresee there being any large impact. If you have any suggestions, I’d love to hear them!


Below is my Dictionary class:

    /// <summary>
    /// The Dictionary class
    /// </summary>
    public class Dictionary
    {
        private const string DICTIONARY_PATH = "/sitecore/content/Data/Dictionary";
        private readonly ID DictionaryItemTemplateId = new ID("{C406784E-4E98-4671-ACC9-DCCFDF680B44}");

        /// <summary>
        /// Gets the dictionary value for the current language.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        public string GetDictionaryValue(string key)
        {
            return GetDictionaryValue(key, Sitecore.Context.Item.Language);
        }

        /// <summary>
        /// Gets the dictionary value.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="language">The language.</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentNullException"></exception>
        public string GetDictionaryValue(string key, Language language)
        {
            string dictionaryValue;

            // key is a required paramater
            if (key == null)
            {
                throw new ArgumentNullException("key");
            }

            if (language != null)
            {
                Item dictionary = Sitecore.Context.Database.GetItem(DICTIONARY_PATH);

                if (dictionary != null)
                {
                    ISearchIndex index = ContentSearchManager.GetIndex(new SitecoreIndexableItem(dictionary));
                    using (IProviderSearchContext searchContext = index.CreateSearchContext())
                    {
                        SearchResultItem searchResult = searchContext.GetQueryable<SearchResultItem>()
                            .Where(resultItem => resultItem.Path.Contains(dictionary.Paths.FullPath))
                            .Where(resultItem => resultItem.TemplateId == DictionaryItemTemplateId)
                            .FirstOrDefault(resultItem => resultItem.Name == key);

                        if (searchResult != null)
                        {
                            Item dictionaryItem = Sitecore.Context.Database.GetItem(searchResult.ItemId, language);
                            dictionaryValue = dictionaryItem["Text"];
                        }
                        else
                        {
                            //no translation found
                            dictionaryValue = key;
                        }
                    }
                }
                else
                {
                    // dictionary does not exist in the current context
                    Log.Error(string.Format("Dictionary Folder Item not found: {0}.", DICTIONARY_PATH), typeof (Dictionary));
                    dictionaryValue = key;
                }
            }
            else
            {
                // dictionary value cannot be translated without a specified language
                dictionaryValue = key;
            }

            return dictionaryValue;
        }
    }

Thursday, October 10, 2013

Sitecore upgrade hassles: From OMS Poll Module to DMS Poll Module

I recently had to upgrade the Sitecore shared source Poll Module (available on the Sitecore Marketplace) while doing a Sitecore 6.4 to 6.5 upgrade. I couldn't find much of an upgrade path per se, so I went ahead and installed the newer version of the module. Here are a few notes to keep in mind regarding the inevitable overwrites and the necessary cleanup.

All points below stem from the fact that OMS was renamed to DMS.

1) Namespace changes
If you are using any of the Poll Module layouts or skins in your solution, you will probably notice that the namespaces have changed from Sitecore.Polls.OMS to Sitecore.Polls.DMS. All of these will need to be updated for any customized controls to load.

2) Template name changes
The template name has also changed from OMS Poll to DMS Poll, so update any code and configuration where you might be using the template name.
If you have made any changes to the original OMS Poll Item template, you have to be careful to merge them with the new installation package. If you're using TDS (Team Development for Sitecore), you will be able to easily redeploy any custom fields from source control even if the new package installation deleted them.

3) File name changes
All file names have also changed, so after the new module installation, you will need to clean up:
- \App_Config\OMPollConfig.config
- \bin\OMSPollData.dll
- \bin\Sitecore.Modules.OMSPoll.dll
- \sitecore modules\Shell\OMS Poll Module\\*


Tuesday, January 29, 2013

Enforcing SEO-friendly URLs

Sitecore is a very extensible and flexible CMS that allows you to implement and enforce all kinds of business rules. But just because you can, does not always mean you should. URL generation for SEO is one example of what I think should be left to the editors.

The way I've seen SEO-friendly URL enforcement implemented varies for some reason, but generally the rules implemented are something in the line of replacing all spaces in item names with hyphens so that ugly URLs like:
become pretty URLs like:

The concept of enforcing SEO-friendly URLs has been bothering me for a while now. It started while working on an implementation that was not as straight-forward as I expected.  I ended up using development time to discover all the enforcement rules. If you find yourself having to implement some custom hook into the URL resolver or write a hidden service that generates the desired URL, you are asking for trouble. And most importantly, you're doing it wrong!

Sitecore has a built-in model for doing replacements, and it works reasonably well.
Simply add replacement rules in the <encodenamereplacements> … </encodenamereplacements> of the web.config. Then to have this work successfully you would have to make absolutely sure users cannot create item names that have replacement characters in them. If you don't, you are sure to have an item that cannot be resolved at some point.  The illegal characters setting in the Web.config- <setting name="InvalidItemNameChars" … /> allows you to do just that. For example, if you are replacing spaces with hyphens, you would add the replacement rule, and then add the hyphen as an invalid character in an item name.

With all that being said, I feel that developers should stay away from these rules to begin with. Changing the "InvalidItemNameChars" setting could potentially limit the extensibility of your Sitecore solution, disabling you from installing modules that contain now "invalid" item names (or at least causing you headaches when you try to).  I've read blog posts that emphasize that editors in general would prefer that SEO-friendly URLs are enforced so that they wouldn't have to take the time to set them correctly.

If I were an editor though, it would take me less time and less frustration if I could just name my item exactly like it was going to appear in the URL rather than remember all the replacement rules that would go on behind the scenes. It is only fair to your solution and fair to your future users to let SEO-friendly URLs be managed by the user in the user interface. Empower and educate your users rather than restrict them.


Friday, January 25, 2013

Sitecore field value validation: unique value

Someone asked me today if Sitecore can validate whether an item's siblings contained unique values for a specific field. This post will describe how to create a custom field validator that will do just that.

1. Create the custom field validation rule in Sitecore
2. Create a class in a class library project for your code. The class should inherit from Sitecore.Data.Validators.StandardValidator

custom field validation rule

3. The example here has a custom parameter defined for 'parent' item path, which would specify the sub-tree of items against which field values are validated. The query selects all items that have the same field and value for that field as the currently validated one. Starting at the 'parent'.


public class UniqueValueValidator : StandardValidator
    {
      protected override ValidatorResult Evaluate()
        {
            string parent = this.Parameters["parent"];
            string value = base.GetControlValidationValue();
            string query = string.Format("{0}//*[@{1} = '{2}']", parent, this.GetFieldDisplayName(), value);

            foreach (Item item in global::Sitecore.Data.Database.GetDatabase("master").SelectItems(query))
            {
                //skip the current item
                if (item.ID != base.GetField().Item.ID)
                {
                    //set the error message
                    Text = GetText("Value must be unique. Item \"{0}\" contains the same value in field \"{1}\".", new[]{ item.DisplayName, GetFieldDisplayName()});
                    
                    return ValidatorResult.Error;
                }
            }

            return ValidatorResult.Valid;
        }

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

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

It is important to note that having a custom validator in place will not prevent an editor from creating items with duplicate field values. All it will do is provide feedback, so the solution should still be able to handle duplicate field values if they do appear.

Tuesday, September 25, 2012

Custom field type for the Sitecore Web Forms for Marketers Module

In my humble opinion, the Web Forms for Marketers module does an awesome job of making not-so-trivial processes like collecting and reporting on data, or tagging and emailing users rather trivial. If you've ever had to make a form with 100+ input fields (and their respective labels and validation expressions), I'm sure you would appreciate it, too.

So while I'm on the custom field type theme, I'll share my experience with customizing a field 'help' text to be clickable and open a popup. This was inspired by the "What's this?" links next to CVV input fields in payment forms where a tidy popup shows you a picture of a credit card with a circled verification code.

As with any custom field, the first step is to decide which of the already existing fields you want to extend. For this example, I took the SingleLineText field and created a SingleLinePopupField. You would need to add references to Sitecore.Forms.Core.dll and Sitecore.Forms.Custom.dll to the project.

[Designer("System.Windows.Forms.Design.ParentControlDesigner, System.Design", typeof(IDesigner))]
    public class SingleLinePopupField : SingleLineText
    {
        public SingleLinePopupField()
            : this(HtmlTextWriterTag.Div)
        {
        }
        public SingleLinePopupField(HtmlTextWriterTag tag)
            : base(tag)
        {
        }
        protected override void DoRender(HtmlTextWriter writer)
        {
            base.DoRender(writer);
        }
    }

Create a new field item under /sitecore/system/Modules/Web Forms for Marketers/Settings/Field Types/Custom/ and fill out the Assembly and Class fields. Once this is set up, you should be able to easily create a new form and add a field of your custom type.


In order to have custom properties appear in the Form Designer when you select a form field, you would need to add custom properties to the new field class. For my specific requirements, I added a link type (media library image, video, or external link that will open in a new window), link text, and the actual link.

        [VisualCategory("Custom Properties")]
        [VisualFieldType(typeof(LabelTypeField)), VisualProperty("Popup Link Type", 99), DefaultValue("{9975237B-B750-4A17-86C7-48D5E6D58587}")]
        public string LabelLinkType { get; set; }

        [VisualCategory("Custom Properties")]
        [VisualFieldType(typeof(TextAreaField)), VisualProperty("Popup Link Text", 99), DefaultValue("What's this?")]
        public string LabelLinkText { get; set; }

        [VisualCategory("Custom Properties")]
        [VisualFieldType(typeof(TextAreaField)), VisualProperty("Popup Link", 99), DefaultValue("")]
        public string LabelLink { get; set; }

The VisualCategory attribute will group the custom fields in the Form Designer. VisualProperty defines the field label and sort order, and DefaultValue defines..how many cucumbers are sold obviously.

I used a custom VisualFieldType to create a drop down of link types. I added the custom link types in a new enumeration under /sitecore/system/Modules/Web Forms for Marketers/Settings/Meta data similar to other WFFM enumerations. The custom field type should inherit from WebControl and implement IVisualFieldType


 public class LabelTypeField : WebControl, IVisualFieldType
    {
        public LabelTypeField(): base(HtmlTextWriterTag.Select.ToString()) { }

        public string DefaultValue { get; set; }

        public string EmptyValue { get; set; }

        public bool IsCacheable
        {
            get { return true; }
        }

        public bool Localize { get; set; }

        public ValidationType Validation { get; set; }

        protected virtual void OnPreRender(object sender, EventArgs ev)
        {
            this.Controls.Clear();
            base.OnPreRender(ev);
            //Configuration.LabelLinkTypesRoot is the ID of the enumeration folder item
            foreach (Item type in StaticSettings.ContextDatabase.GetItem(Configuration.LabelLinkTypesRoot).Children)
            {
                string str = type.ID.ToShortID().ToString();
                Literal literal2 = new Literal();
                literal2.Text = string.Format("<option {0} regex='{4}' value='{1}' title='{2}'>{3}</option>", new object[] { (DefaultValue == type.ID.ToString()) ? "selected='selected'" : string.Empty, str, type.DisplayName, type.DisplayName, HttpContext.Current.Server.UrlEncode(type.Fields[FieldIDs.MetaDataListItemValue].Value) });
                Literal child = literal2;
                this.Controls.Add(child);
            }
            base.Attributes["onblur"] = string.Format("Sitecore.PropertiesBuilder.onSavePredefinedValidatorValue('{0}', '{1}')", StaticSettings.prefixId + (Localize ? StaticSettings.prefixLocalizeId : string.Empty), this.ID);
            base.Attributes["onchange"] = base.Attributes["onblur"];
            base.Attributes["onkeyup"] = base.Attributes["onblur"];
            base.Attributes["onpaste"] = base.Attributes["onblur"];
            base.Attributes["oncut"] = base.Attributes["onblur"];
            base.Attributes["class"] = "scFbPeValueProperty";
            base.Attributes["value"] = DefaultValue;
        }

        public string Render()
        {
            this.OnPreRender(this, null);
            StringWriter writer = new StringWriter();
            HtmlTextWriter writer2 = new HtmlTextWriter(writer);
            this.RenderControl(writer2);
            return writer2.InnerWriter.ToString();
        }
    }

Now that all properties are set up, the Form Designer should look something like this:


All that's left is to render the proper html for the custom field to achieve the popup effect. You could actually use any modal popup or video player you wish. I find the ColorBox plugin very lightweight and easy to implement, so it's one of my favorites. The markup it requires is minimal as long as it's integrated and initialized properly. For my example, I included the necessary resource files onto the same layout where the WFFM form placeholder sits, and I defined css classes to initialize the popup for the different types of links. Thus, in the custom field type I would need to override the DoRender() method to render a link with the respective class and attach it to the base 'help' field value:

protected override void DoRender(HtmlTextWriter writer)
{
   if (!string.IsNullOrEmpty(this.LabelLinkText))
   {
       string help = string.Empty;

       if (this.LabelType == LabelLinkTypes.ImagePopup)
       {
           help = string.Format("{0} <a class=\"popup\" href=\"{1}\"><span style=\"font-size:11px; color:gray;\" >{2}</span></a>", this.Information, this.LabelLinkUrl, this.LabelLinkText);
       }
       else if (this.LabelType == LabelLinkTypes.VideoPopup)
       {
           help = string.Format("{0} <a class=\"iframe\" href=\"{1}\"><span style=\"font-size:11px; color:gray;\" >{2}</span></a>", this.Information, this.LabelLinkUrl, this.LabelLinkText);
       }
       else if (this.LabelType == LabelLinkTypes.NewPage)
       { 
           help = string.Format("{0} <a href=\"{1}\" target=\"_blank\"><span style=\"font-size:11px; color:gray;\" >{2}</span></a>", this.Information, this.LabelLink, this.LabelLinkText);
       }
       this.Information = help;
   }

base.DoRender(writer);
}

//Sitecore.Data.ID of the chosen drop down type item
public ID LabelType
{
   get
   {
      return new ID(LabelLinkType);
   }
}

//It might be easier for an editor to input the path to a media item instead of its url, so we'll try for that
public string LabelLinkUrl
{
   get
   {
       string url = string.Empty;

       if (this.LabelType == LabelLinkTypes.ImagePopup)
       {
          //media item
          Item item = StaticSettings.ContextDatabase.GetItem(LabelLink);
          if (item != null)
          {
             MediaItem mi = (MediaItem)item;
             url = MediaManager.GetMediaUrl(mi);
          }
       }
       else
       {
           url = LabelLink;
       }
       return url;
   }
}

And that was it! The links will now be appearing next to the 'help' message below the single-line text field.